Last active
October 28, 2024 12:16
-
-
Save miaoles/a88995378271c2ec8ec1d9a87221d7d6 to your computer and use it in GitHub Desktop.
vulkan-tutorial.com Triangle with Odin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // vulkan-tutorial.com Triangle with Odin | |
| // also has personal tweaks/additions, explicit naming, entire enum names | |
| package main | |
| import fmt "core:fmt" | |
| import math "core:math" | |
| import os "core:os" | |
| import str "core:strings" | |
| import glfw "vendor:glfw" | |
| import vk "vendor:vulkan" | |
| when ODIN_DEBUG == true { | |
| ENABLE_VALIDATION_LAYERS :: true | |
| } else { | |
| ENABLE_VALIDATION_LAYERS :: false | |
| } | |
| WIDTH :: 1280 | |
| HEIGHT :: 720 | |
| TITLE :: "Vulkan Engine" | |
| VALIDATION_LAYERS := []cstring{"VK_LAYER_KHRONOS_validation"} | |
| DEVICE_EXTENSIONS := []cstring{"VK_KHR_swapchain"} | |
| Context :: struct { | |
| instance: vk.Instance, | |
| window: glfw.WindowHandle, | |
| debug_messenger: vk.DebugUtilsMessengerEXT, | |
| physical_device: vk.PhysicalDevice, | |
| logical_device: vk.Device, | |
| graphics_queue: vk.Queue, | |
| surface: vk.SurfaceKHR, | |
| present_queue: vk.Queue, | |
| swap_chain: vk.SwapchainKHR, | |
| swap_chain_images: [dynamic]vk.Image, | |
| swap_chain_image_format: vk.Format, | |
| swap_chain_extent: vk.Extent2D, | |
| swap_chain_image_views: [dynamic]vk.ImageView, | |
| render_pass: vk.RenderPass, | |
| pipeline_layout: vk.PipelineLayout, | |
| graphics_pipeline: vk.Pipeline, | |
| swap_chain_framebuffers: [dynamic]vk.Framebuffer, | |
| command_pool: vk.CommandPool, | |
| command_buffer: vk.CommandBuffer, | |
| image_available_semaphore: vk.Semaphore, | |
| render_finished_sempahore: vk.Semaphore, | |
| in_flight_fence: vk.Fence, | |
| } | |
| Queue_Family_Flags :: enum { | |
| Graphics_Family, | |
| Present_Family, | |
| } | |
| Queue_Family_Indices :: struct { | |
| graphics_family: Maybe(u32), | |
| present_family: Maybe(u32), | |
| } | |
| Swap_Chain_Support_Details :: struct { | |
| capabilities: vk.SurfaceCapabilitiesKHR, | |
| formats: [dynamic]vk.SurfaceFormatKHR, | |
| present_modes: [dynamic]vk.PresentModeKHR, | |
| } | |
| main :: proc() { | |
| using ctx: Context | |
| if !run_app(&ctx) do return | |
| return | |
| } | |
| run_app :: proc(using ctx: ^Context) -> b32 { | |
| fmt.println("init_window") | |
| init_window(ctx) | |
| fmt.println("init_vulkan") | |
| init_vulkan(ctx) | |
| fmt.println("main_loop") | |
| main_loop(ctx) | |
| fmt.println("end_app") | |
| end_app(ctx) | |
| return true | |
| } | |
| end_app :: proc(using ctx: ^Context) { | |
| fmt.println("term_vulkan") | |
| term_vulkan(ctx) | |
| fmt.println("term_window") | |
| term_window(ctx) | |
| } | |
| init_window :: proc(using ctx: ^Context) { | |
| fmt.println("glfw.Init") | |
| glfw.Init() | |
| glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API) | |
| glfw.WindowHint(glfw.RESIZABLE, glfw.FALSE) | |
| fmt.println("glfw.CreateWindow") | |
| window = glfw.CreateWindow(WIDTH, HEIGHT, TITLE, nil, nil) | |
| } | |
| term_window :: proc(using ctx: ^Context) { | |
| fmt.println("glfw.DestroyWindow") | |
| glfw.DestroyWindow(window) | |
| fmt.println("glfw.Terminate") | |
| glfw.Terminate() | |
| } | |
| init_vulkan :: proc(using ctx: ^Context) { | |
| fmt.println("create_instance") | |
| create_instance(ctx) | |
| fmt.println("create_debug_messenger") | |
| setup_debug_messenger(ctx) | |
| fmt.println("create_surface") | |
| create_surface(ctx) | |
| fmt.println("choose_physical_device") | |
| choose_physical_device(ctx) | |
| fmt.println("create_logical_device") | |
| create_logical_device(ctx) | |
| fmt.println("create_swap_chain") | |
| create_swap_chain(ctx) | |
| fmt.println("create_image_views") | |
| create_image_views(ctx) | |
| fmt.println("create_render_pass") | |
| create_render_pass(ctx) | |
| fmt.println("create_graphics_pipeline") | |
| create_graphics_pipeline(ctx) | |
| fmt.println("create_framebuffers") | |
| create_framebuffers(ctx) | |
| fmt.println("create_command_pool") | |
| create_command_pool(ctx) | |
| fmt.println("create_command_buffer") | |
| create_command_buffer(ctx) | |
| fmt.println("create_sync_objects") | |
| create_sync_objects(ctx) | |
| } | |
| term_vulkan :: proc(using ctx: ^Context) { | |
| fmt.println("vk.DestroySemaphore(s)") | |
| vk.DestroySemaphore(logical_device, image_available_semaphore, nil) | |
| vk.DestroySemaphore(logical_device, render_finished_sempahore, nil) | |
| fmt.println("vk.Fence(s)") | |
| vk.DestroyFence(logical_device, in_flight_fence, nil) | |
| fmt.println("vk.DestroyCommandPool") | |
| vk.DestroyCommandPool(logical_device, command_pool, nil) | |
| fmt.println("vk.DestroyFramebuffer(s)") | |
| for framebuffer in swap_chain_framebuffers { | |
| vk.DestroyFramebuffer(logical_device, framebuffer, nil) | |
| } | |
| fmt.println("vk.DestroyPipeline") | |
| vk.DestroyPipeline(logical_device, graphics_pipeline, nil) | |
| fmt.println("vk.DestroyPipelineLayout") | |
| vk.DestroyPipelineLayout(logical_device, pipeline_layout, nil) | |
| fmt.println("vk.DestroyRenderPass") | |
| vk.DestroyRenderPass(logical_device, render_pass, nil) | |
| fmt.println("vk.DestroyImageView(s)") | |
| for image_view in swap_chain_image_views { | |
| vk.DestroyImageView(logical_device, image_view, nil) | |
| } | |
| fmt.println("vk.DestroySwapchainKHR") | |
| vk.DestroySwapchainKHR(logical_device, swap_chain, nil) | |
| fmt.println("vk.DestroyDevice") | |
| vk.DestroyDevice(logical_device, nil) | |
| fmt.println("vk.DestroySurfaceKHR") | |
| vk.DestroySurfaceKHR(instance, surface, nil) | |
| if ENABLE_VALIDATION_LAYERS { | |
| fmt.println("destroy_debug_utils_messenger") | |
| destroy_debug_utils_messenger(instance, debug_messenger, nil) | |
| } | |
| fmt.println("vk.DestroyInstance") | |
| vk.DestroyInstance(instance, nil) | |
| } | |
| main_loop :: proc(using ctx: ^Context) { | |
| for (!glfw.WindowShouldClose(window)) { | |
| glfw.PollEvents() | |
| draw_frame(ctx) | |
| } | |
| vk.DeviceWaitIdle(logical_device) | |
| } | |
| create_instance :: proc(using ctx: ^Context) { | |
| vk.load_proc_addresses(rawptr(glfw.GetInstanceProcAddress)) | |
| if ENABLE_VALIDATION_LAYERS { | |
| if check_validation_layer_support() { | |
| fmt.println("Validation layers requested and found.") | |
| } else { | |
| fmt.eprintln("Validation layers requested and not found.") | |
| } | |
| } | |
| app_info: vk.ApplicationInfo | |
| app_info.sType = vk.StructureType.APPLICATION_INFO | |
| app_info.pApplicationName = "Hellope Triangle" | |
| app_info.applicationVersion = vk.MAKE_VERSION(1, 0, 0) | |
| app_info.pEngineName = TITLE | |
| app_info.engineVersion = vk.MAKE_VERSION(1, 0, 0) | |
| app_info.apiVersion = vk.API_VERSION_1_0 | |
| info: vk.InstanceCreateInfo | |
| info.sType = vk.StructureType.INSTANCE_CREATE_INFO | |
| info.pApplicationInfo = &app_info | |
| extensions := get_required_extensions() | |
| info.enabledExtensionCount = u32(len(extensions)) | |
| info.ppEnabledExtensionNames = raw_data(extensions) | |
| debug_info: vk.DebugUtilsMessengerCreateInfoEXT | |
| if (ENABLE_VALIDATION_LAYERS) { | |
| info.enabledLayerCount = u32(len(VALIDATION_LAYERS)) | |
| info.ppEnabledLayerNames = raw_data(VALIDATION_LAYERS) | |
| populate_debug_messenger_info(&debug_info) | |
| info.pNext = cast(^vk.DebugUtilsMessengerCreateInfoEXT)&debug_info | |
| } else { | |
| info.enabledLayerCount = 0 | |
| info.pNext = nil | |
| } | |
| result: vk.Result = vk.CreateInstance(&info, nil, &instance) | |
| if result != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create Vulkan instance.") | |
| } else { | |
| fmt.println("Successfully created Vulkan instance.") | |
| } | |
| vk.load_proc_addresses_instance(instance) | |
| } | |
| debug_callback :: proc( | |
| message_severity: vk.DebugUtilsMessageSeverityFlagEXT, | |
| message_type: vk.DebugUtilsMessageTypeFlagEXT, | |
| p_callback_data: ^vk.DebugUtilsMessengerCallbackDataEXT, | |
| p_user_data: rawptr, | |
| ) -> b32 { | |
| fmt.eprintln("Validation Layer: ", p_callback_data.pMessage) | |
| return false | |
| } | |
| check_validation_layer_support :: proc() -> b32 { | |
| layer_count: u32 | |
| vk.EnumerateInstanceLayerProperties(&layer_count, nil) | |
| available_layers := make([]vk.LayerProperties, layer_count) | |
| vk.EnumerateInstanceLayerProperties(&layer_count, raw_data(available_layers)) | |
| wanted_layer_found: b32 = false | |
| for wanted_layer_cstring in VALIDATION_LAYERS { | |
| for available_layer_properties in available_layers { | |
| available_layer_string := convert_bytes_to_string(available_layer_properties.layerName) | |
| if str.compare(string(wanted_layer_cstring), available_layer_string) == 0 { | |
| wanted_layer_found = true | |
| break | |
| } | |
| } | |
| } | |
| return wanted_layer_found | |
| } | |
| convert_bytes_to_string :: proc(bytes: [256]u8) -> string { | |
| bytes_clone := bytes | |
| builder := str.clone_from_bytes(bytes_clone[:]) | |
| cstring := str.clone_to_cstring(builder) | |
| return string(cstring) | |
| } | |
| get_required_extensions :: proc() -> []cstring { | |
| glfw_extensions := glfw.GetRequiredInstanceExtensions() | |
| extensions := make([dynamic]string, len(glfw_extensions)) | |
| for index in 0 ..< len(glfw_extensions) { | |
| extensions[index] = string(glfw_extensions[index]) | |
| } | |
| if (ENABLE_VALIDATION_LAYERS) { | |
| append(&extensions, string(vk.EXT_DEBUG_UTILS_EXTENSION_NAME)) | |
| } | |
| result := make([]cstring, len(extensions)) | |
| for index in 0 ..< len(extensions) { | |
| result[index] = str.clone_to_cstring(extensions[index]) | |
| } | |
| fmt.println("Extensions Result: ", result) | |
| return result | |
| } | |
| setup_debug_messenger :: proc(using ctx: ^Context) { | |
| if !ENABLE_VALIDATION_LAYERS do return | |
| info: vk.DebugUtilsMessengerCreateInfoEXT | |
| populate_debug_messenger_info(&info) | |
| if (create_debug_utils_messenger(instance, &info, nil, &debug_messenger) != vk.Result.SUCCESS) { | |
| fmt.eprintln("Failed to set up debug messenger.") | |
| } | |
| info.pUserData = nil | |
| } | |
| populate_debug_messenger_info :: proc(info: ^vk.DebugUtilsMessengerCreateInfoEXT) { | |
| info.sType = vk.StructureType.DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT | |
| info.messageSeverity = vk.DebugUtilsMessageSeverityFlagsEXT{.VERBOSE, .WARNING, .ERROR} | |
| info.messageType = vk.DebugUtilsMessageTypeFlagsEXT{.GENERAL, .VALIDATION, .PERFORMANCE} | |
| info.pfnUserCallback = vk.ProcDebugUtilsMessengerCallbackEXT(debug_callback) | |
| } | |
| create_debug_utils_messenger :: proc( | |
| instance: vk.Instance, | |
| p_info: ^vk.DebugUtilsMessengerCreateInfoEXT, | |
| p_allocator: ^vk.AllocationCallbacks, | |
| p_debug_messenger: ^vk.DebugUtilsMessengerEXT, | |
| ) -> vk.Result { | |
| procedure := auto_cast vk.ProcCreateDebugUtilsMessengerEXT(vk.GetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")) | |
| if procedure != nil { | |
| return procedure(instance, p_info, p_allocator, p_debug_messenger) | |
| } else { | |
| return vk.Result.ERROR_EXTENSION_NOT_PRESENT | |
| } | |
| } | |
| destroy_debug_utils_messenger :: proc(instance: vk.Instance, debug_messenger: vk.DebugUtilsMessengerEXT, p_allocator: ^vk.AllocationCallbacks) { | |
| procedure := auto_cast vk.ProcDestroyDebugUtilsMessengerEXT(vk.GetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")) | |
| if procedure != nil { | |
| procedure(instance, debug_messenger, p_allocator) | |
| } | |
| } | |
| create_surface :: proc(using ctx: ^Context) { | |
| if (glfw.CreateWindowSurface(instance, window, nil, &surface)) != .SUCCESS { | |
| fmt.eprintln("Failed to create window surface.") | |
| } | |
| } | |
| choose_physical_device :: proc(using ctx: ^Context) { | |
| device_count: u32 = 0 | |
| vk.EnumeratePhysicalDevices(instance, &device_count, nil) | |
| if device_count == 0 { | |
| fmt.eprintln("Failed to find GPUs with Vulkan support.") | |
| } | |
| physical_devices := make([]vk.PhysicalDevice, device_count) | |
| vk.EnumeratePhysicalDevices(instance, &device_count, raw_data(physical_devices)) | |
| for device in physical_devices { | |
| if is_device_suitable(ctx, device) { | |
| physical_device = device | |
| break | |
| } | |
| } | |
| if physical_device == nil { | |
| fmt.eprintln("Failed to find suitable GPU.") | |
| } | |
| device_properties: vk.PhysicalDeviceProperties | |
| vk.GetPhysicalDeviceProperties(physical_device, &device_properties) | |
| device_features: vk.PhysicalDeviceFeatures | |
| vk.GetPhysicalDeviceFeatures(physical_device, &device_features) | |
| } | |
| create_logical_device :: proc(using ctx: ^Context) { | |
| indices: Queue_Family_Indices = find_queue_families(ctx, physical_device) | |
| queue_infos := make([dynamic]vk.DeviceQueueCreateInfo) | |
| queue_families_unique := bit_set[Queue_Family_Flags]{.Graphics_Family, .Present_Family} | |
| queue_priority: f32 = 1.0 | |
| for queue_family in queue_families_unique { | |
| queue_info: vk.DeviceQueueCreateInfo | |
| queue_info.sType = vk.StructureType.DEVICE_QUEUE_CREATE_INFO | |
| queue_info.queueFamilyIndex = u32(queue_family) | |
| queue_info.queueCount = 1 | |
| queue_info.pQueuePriorities = &queue_priority | |
| append(&queue_infos, queue_info) | |
| } | |
| device_features: vk.PhysicalDeviceFeatures | |
| info: vk.DeviceCreateInfo | |
| info.sType = vk.StructureType.DEVICE_CREATE_INFO | |
| info.queueCreateInfoCount = u32(len(queue_infos)) | |
| info.pQueueCreateInfos = raw_data(queue_infos) | |
| info.pEnabledFeatures = &device_features | |
| info.enabledExtensionCount = u32(len(DEVICE_EXTENSIONS)) | |
| info.ppEnabledExtensionNames = raw_data(DEVICE_EXTENSIONS) | |
| if ENABLE_VALIDATION_LAYERS { | |
| info.enabledLayerCount = u32(len(VALIDATION_LAYERS)) | |
| info.ppEnabledLayerNames = raw_data(VALIDATION_LAYERS) | |
| } else { | |
| info.enabledLayerCount = 0 | |
| } | |
| if vk.CreateDevice(physical_device, &info, nil, &logical_device) != .SUCCESS { | |
| fmt.eprintln("Failed to create logical device.") | |
| } | |
| vk.GetDeviceQueue(logical_device, indices.graphics_family.(u32), 0, &graphics_queue) | |
| vk.GetDeviceQueue(logical_device, indices.present_family.(u32), 0, &present_queue) | |
| } | |
| is_device_suitable :: proc(using ctx: ^Context, device: vk.PhysicalDevice) -> b32 { | |
| indices: Queue_Family_Indices = find_queue_families(ctx, device) | |
| extensions_supported := check_device_extension_support(device) | |
| swap_chain_adequate: b32 = false | |
| if extensions_supported { | |
| swap_chain_support := query_swap_chain_support(ctx, device) | |
| swap_chain_adequate = !(len(swap_chain_support.formats) == 0) && !(len(swap_chain_support.present_modes) == 0) | |
| } | |
| return (indices.graphics_family != nil) && extensions_supported && swap_chain_adequate | |
| } | |
| find_queue_families :: proc(using ctx: ^Context, device: vk.PhysicalDevice) -> Queue_Family_Indices { | |
| indices: Queue_Family_Indices | |
| queue_family_count: u32 = 0 | |
| vk.GetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nil) | |
| queue_families := make([]vk.QueueFamilyProperties, queue_family_count) | |
| queue_families_raw := raw_data(queue_families) | |
| vk.GetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families_raw) | |
| i: int = 0 | |
| for queue_family in queue_families { | |
| if vk.QueueFlag.GRAPHICS in queue_family.queueFlags { | |
| indices.graphics_family = u32(i) | |
| } | |
| present_support: b32 = false | |
| vk.GetPhysicalDeviceSurfaceSupportKHR(device, u32(i), surface, &present_support) | |
| if present_support do indices.present_family = u32(i) | |
| if indices.graphics_family != nil && present_support { | |
| break | |
| } | |
| i += 1 | |
| } | |
| return indices | |
| } | |
| check_device_extension_support :: proc(device: vk.PhysicalDevice) -> b32 { | |
| extension_count: u32 | |
| vk.EnumerateDeviceExtensionProperties(device, nil, &extension_count, nil) | |
| available_extensions := make([]vk.ExtensionProperties, extension_count) | |
| vk.EnumerateDeviceExtensionProperties(device, nil, &extension_count, raw_data(available_extensions)) | |
| required_extensions := make(map[string]b32, len(DEVICE_EXTENSIONS)) | |
| for extension in DEVICE_EXTENSIONS { | |
| fmt.println("Required Extension: ", extension) | |
| required_extensions[string(extension)] = true | |
| } | |
| for extension in available_extensions { | |
| delete_key(&required_extensions, convert_bytes_to_string(extension.extensionName)) | |
| } | |
| return len(required_extensions) == 0 | |
| } | |
| create_swap_chain :: proc(using ctx: ^Context) { | |
| swap_chain_support: Swap_Chain_Support_Details = query_swap_chain_support(ctx, physical_device) | |
| surface_format: vk.SurfaceFormatKHR = choose_swap_surface_format(swap_chain_support.formats[:]) | |
| present_mode: vk.PresentModeKHR = choose_swap_present_mode(swap_chain_support.present_modes[:]) | |
| extent: vk.Extent2D = choose_swap_extent(ctx, swap_chain_support.capabilities) | |
| image_count: u32 = swap_chain_support.capabilities.minImageCount + 1 | |
| if (swap_chain_support.capabilities.maxImageCount > 0) && (image_count > swap_chain_support.capabilities.maxImageCount) { | |
| image_count = swap_chain_support.capabilities.maxImageCount | |
| } | |
| info: vk.SwapchainCreateInfoKHR | |
| info.sType = vk.StructureType.SWAPCHAIN_CREATE_INFO_KHR | |
| info.surface = surface | |
| info.minImageCount = image_count | |
| info.imageFormat = surface_format.format | |
| info.imageColorSpace = surface_format.colorSpace | |
| info.imageExtent = extent | |
| info.imageArrayLayers = 1 | |
| info.imageUsage = {vk.ImageUsageFlag.COLOR_ATTACHMENT} | |
| indices: Queue_Family_Indices = find_queue_families(ctx, physical_device) | |
| queue_family_indices: []u32 = {indices.graphics_family.(u32), indices.present_family.(u32)} | |
| if indices.graphics_family != indices.present_family { | |
| info.imageSharingMode = vk.SharingMode.CONCURRENT | |
| info.queueFamilyIndexCount = 2 | |
| info.pQueueFamilyIndices = raw_data(queue_family_indices) | |
| } else { | |
| info.imageSharingMode = vk.SharingMode.EXCLUSIVE | |
| info.queueFamilyIndexCount = 0 | |
| info.pQueueFamilyIndices = nil | |
| } | |
| info.preTransform = swap_chain_support.capabilities.currentTransform | |
| info.compositeAlpha = {vk.CompositeAlphaFlagsKHR.OPAQUE} | |
| info.presentMode = present_mode | |
| info.clipped = true | |
| info.oldSwapchain = {} | |
| if vk.CreateSwapchainKHR(logical_device, &info, nil, &swap_chain) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create swap chain.") | |
| } | |
| vk.GetSwapchainImagesKHR(logical_device, swap_chain, &image_count, nil) | |
| resize(&swap_chain_images, int(image_count)) | |
| vk.GetSwapchainImagesKHR(logical_device, swap_chain, &image_count, raw_data(swap_chain_images)) | |
| swap_chain_image_format = surface_format.format | |
| swap_chain_extent = extent | |
| } | |
| query_swap_chain_support :: proc(using ctx: ^Context, device: vk.PhysicalDevice) -> Swap_Chain_Support_Details { | |
| details: Swap_Chain_Support_Details | |
| vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities) | |
| format_count: u32 | |
| vk.GetPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, nil) | |
| if format_count != 0 { | |
| resize(&details.formats, int(format_count)) | |
| vk.GetPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, raw_data(details.formats)) | |
| } | |
| present_mode_count: u32 | |
| vk.GetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, nil) | |
| if present_mode_count != 0 { | |
| resize(&details.present_modes, int(present_mode_count)) | |
| vk.GetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, raw_data(details.present_modes)) | |
| } | |
| return details | |
| } | |
| choose_swap_surface_format :: proc(available_formats: []vk.SurfaceFormatKHR) -> vk.SurfaceFormatKHR { | |
| for available_format in available_formats { | |
| if available_format.format == vk.Format.B8G8R8A8_SRGB && | |
| available_format.colorSpace == (vk.ColorSpaceKHR.COLORSPACE_SRGB_NONLINEAR | vk.ColorSpaceKHR.SRGB_NONLINEAR) { | |
| return available_format | |
| } | |
| } | |
| return available_formats[0] | |
| } | |
| choose_swap_present_mode :: proc(available_present_modes: []vk.PresentModeKHR) -> vk.PresentModeKHR { | |
| for available_present_mode in available_present_modes { | |
| if available_present_mode == vk.PresentModeKHR.MAILBOX { | |
| return available_present_mode | |
| } | |
| } | |
| return vk.PresentModeKHR.FIFO | |
| } | |
| choose_swap_extent :: proc(using ctx: ^Context, capabilities: vk.SurfaceCapabilitiesKHR) -> vk.Extent2D { | |
| if (capabilities.currentExtent.width) != math.max(u32) { | |
| return capabilities.currentExtent | |
| } else { | |
| framebuffer_width, framebuffer_height := glfw.GetFramebufferSize(window) | |
| actual_extent: vk.Extent2D = {u32(framebuffer_width), u32(framebuffer_height)} | |
| actual_extent.width = math.clamp(actual_extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width) | |
| actual_extent.height = math.clamp(actual_extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) | |
| return actual_extent | |
| } | |
| } | |
| create_image_views :: proc(using ctx: ^Context) { | |
| resize(&swap_chain_image_views, len(swap_chain_images)) | |
| for index in 0 ..< len(swap_chain_images) { | |
| info: vk.ImageViewCreateInfo | |
| info.sType = vk.StructureType.IMAGE_VIEW_CREATE_INFO | |
| info.image = swap_chain_images[index] | |
| info.viewType = vk.ImageViewType.D2 | |
| info.format = swap_chain_image_format | |
| info.components.r = vk.ComponentSwizzle.IDENTITY | |
| info.components.g = vk.ComponentSwizzle.IDENTITY | |
| info.components.b = vk.ComponentSwizzle.IDENTITY | |
| info.components.a = vk.ComponentSwizzle.IDENTITY | |
| info.subresourceRange.aspectMask = {vk.ImageAspectFlag.COLOR} | |
| info.subresourceRange.baseMipLevel = 0 | |
| info.subresourceRange.levelCount = 1 | |
| info.subresourceRange.baseArrayLayer = 0 | |
| info.subresourceRange.layerCount = 1 | |
| if (vk.CreateImageView(logical_device, &info, nil, &swap_chain_image_views[index])) != vk.Result.SUCCESS { | |
| fmt.println("Failed to create image views.") | |
| } | |
| } | |
| } | |
| create_graphics_pipeline :: proc(using ctx: ^Context) { | |
| vertex_shader_code, vertex_shader_code_success := read_file_bytes("vert.spv") | |
| if vertex_shader_code_success { | |
| fmt.println("Successfully read vertex shader code.") | |
| } else { | |
| fmt.eprintln("Failed to read vertex shader code.") | |
| } | |
| fragment_shader_code, fragment_shader_code_success := read_file_bytes("frag.spv") | |
| if fragment_shader_code_success { | |
| fmt.println("Successfully read fragment shader code.") | |
| } else { | |
| fmt.eprintln("Failed to read fragment shader code.") | |
| } | |
| vertex_shader_module, vertex_shader_module_success := create_shader_module(ctx, vertex_shader_code) | |
| defer vk.DestroyShaderModule(logical_device, vertex_shader_module, nil) | |
| fragment_shader_module, fragment_shader_module_success := create_shader_module(ctx, fragment_shader_code) | |
| defer vk.DestroyShaderModule(logical_device, fragment_shader_module, nil) | |
| vertex_shader_stage_info: vk.PipelineShaderStageCreateInfo | |
| vertex_shader_stage_info.sType = vk.StructureType.PIPELINE_SHADER_STAGE_CREATE_INFO | |
| vertex_shader_stage_info.stage = {vk.ShaderStageFlag.VERTEX} | |
| vertex_shader_stage_info.module = vertex_shader_module | |
| vertex_shader_stage_info.pName = "main" | |
| vertex_shader_stage_info.pSpecializationInfo = nil | |
| fragment_shader_stage_info: vk.PipelineShaderStageCreateInfo | |
| fragment_shader_stage_info.sType = vk.StructureType.PIPELINE_SHADER_STAGE_CREATE_INFO | |
| fragment_shader_stage_info.stage = {vk.ShaderStageFlag.FRAGMENT} | |
| fragment_shader_stage_info.module = fragment_shader_module | |
| fragment_shader_stage_info.pName = "main" | |
| fragment_shader_stage_info.pSpecializationInfo = nil | |
| shader_stages: []vk.PipelineShaderStageCreateInfo = {vertex_shader_stage_info, fragment_shader_stage_info} | |
| vertex_input_info: vk.PipelineVertexInputStateCreateInfo | |
| vertex_input_info.sType = vk.StructureType.PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO | |
| vertex_input_info.vertexBindingDescriptionCount = 0 | |
| vertex_input_info.pVertexBindingDescriptions = nil | |
| vertex_input_info.vertexAttributeDescriptionCount = 0 | |
| vertex_input_info.pVertexAttributeDescriptions = nil | |
| input_assembly_info: vk.PipelineInputAssemblyStateCreateInfo | |
| input_assembly_info.sType = vk.StructureType.PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO | |
| input_assembly_info.topology = vk.PrimitiveTopology.TRIANGLE_LIST | |
| input_assembly_info.primitiveRestartEnable = false // vk.FALSE | |
| viewport: vk.Viewport = {} | |
| viewport.x = 0.0 | |
| viewport.y = 0.0 | |
| viewport.width = f32(swap_chain_extent.width) | |
| viewport.height = f32(swap_chain_extent.height) | |
| viewport.minDepth = 0.0 | |
| viewport.maxDepth = 1.0 | |
| scissor: vk.Rect2D | |
| scissor.offset = {0, 0} | |
| scissor.extent = swap_chain_extent | |
| dynamic_states: []vk.DynamicState = {vk.DynamicState.VIEWPORT, vk.DynamicState.SCISSOR} | |
| dynamic_state_info: vk.PipelineDynamicStateCreateInfo | |
| dynamic_state_info.sType = vk.StructureType.PIPELINE_DYNAMIC_STATE_CREATE_INFO | |
| dynamic_state_info.dynamicStateCount = u32(len(dynamic_states)) | |
| dynamic_state_info.pDynamicStates = raw_data(dynamic_states) | |
| viewport_state_info: vk.PipelineViewportStateCreateInfo | |
| viewport_state_info.sType = vk.StructureType.PIPELINE_VIEWPORT_STATE_CREATE_INFO | |
| viewport_state_info.viewportCount = 1 | |
| //viewport_state_info.pViewports = &viewport | |
| viewport_state_info.scissorCount = 1 | |
| //viewport_state_info.pScissors = &scissor | |
| rasterizer_info: vk.PipelineRasterizationStateCreateInfo | |
| rasterizer_info.sType = vk.StructureType.PIPELINE_RASTERIZATION_STATE_CREATE_INFO | |
| rasterizer_info.depthClampEnable = false // vk.FALSE | |
| rasterizer_info.rasterizerDiscardEnable = false // vk.FALSE | |
| rasterizer_info.polygonMode = vk.PolygonMode.FILL | |
| rasterizer_info.lineWidth = 1.0 | |
| rasterizer_info.cullMode = {vk.CullModeFlag.BACK} | |
| rasterizer_info.frontFace = vk.FrontFace.CLOCKWISE | |
| rasterizer_info.depthBiasEnable = false // vk.FALSE | |
| rasterizer_info.depthBiasConstantFactor = 0.0 | |
| rasterizer_info.depthBiasClamp = 0.0 | |
| rasterizer_info.depthBiasSlopeFactor = 0.0 | |
| multisampling_info: vk.PipelineMultisampleStateCreateInfo | |
| multisampling_info.sType = vk.StructureType.PIPELINE_MULTISAMPLE_STATE_CREATE_INFO | |
| multisampling_info.sampleShadingEnable = false // vk.FALSE | |
| multisampling_info.rasterizationSamples = {vk.SampleCountFlag._1} | |
| multisampling_info.minSampleShading = 1.0 | |
| multisampling_info.pSampleMask = nil | |
| multisampling_info.alphaToCoverageEnable = false // vk.FALSE | |
| multisampling_info.alphaToOneEnable = false // vk.FALSE | |
| color_blend_attachment_state: vk.PipelineColorBlendAttachmentState | |
| color_blend_attachment_state.colorWriteMask = {vk.ColorComponentFlag.R, vk.ColorComponentFlag.G, vk.ColorComponentFlag.B, vk.ColorComponentFlag.A} | |
| //color_blend_attachment_state.blendEnable = false // vk.FALSE | |
| //color_blend_attachment_state.srcColorBlendFactor = vk.BlendFactor.ONE | |
| //color_blend_attachment_state.dstColorBlendFactor = vk.BlendFactor.ZERO | |
| color_blend_attachment_state.blendEnable = true // vk.TRUE | |
| color_blend_attachment_state.srcColorBlendFactor = vk.BlendFactor.SRC_ALPHA | |
| color_blend_attachment_state.dstColorBlendFactor = vk.BlendFactor.ONE_MINUS_SRC_ALPHA | |
| color_blend_attachment_state.colorBlendOp = vk.BlendOp.ADD | |
| color_blend_attachment_state.srcAlphaBlendFactor = vk.BlendFactor.ONE | |
| color_blend_attachment_state.dstAlphaBlendFactor = vk.BlendFactor.ZERO | |
| color_blend_attachment_state.alphaBlendOp = vk.BlendOp.ADD | |
| color_blend_state_info: vk.PipelineColorBlendStateCreateInfo | |
| color_blend_state_info.sType = vk.StructureType.PIPELINE_COLOR_BLEND_STATE_CREATE_INFO | |
| color_blend_state_info.logicOpEnable = false // vk.FALSE | |
| color_blend_state_info.logicOp = vk.LogicOp.COPY | |
| color_blend_state_info.attachmentCount = 1 | |
| color_blend_state_info.pAttachments = &color_blend_attachment_state | |
| color_blend_state_info.blendConstants[0] = 0.0 | |
| color_blend_state_info.blendConstants[1] = 0.0 | |
| color_blend_state_info.blendConstants[2] = 0.0 | |
| color_blend_state_info.blendConstants[3] = 0.0 | |
| pipeline_layout_info: vk.PipelineLayoutCreateInfo | |
| pipeline_layout_info.sType = vk.StructureType.PIPELINE_LAYOUT_CREATE_INFO | |
| pipeline_layout_info.setLayoutCount = 0 | |
| pipeline_layout_info.pSetLayouts = nil | |
| pipeline_layout_info.pushConstantRangeCount = 0 | |
| pipeline_layout_info.pPushConstantRanges = nil | |
| if vk.CreatePipelineLayout(logical_device, &pipeline_layout_info, nil, &pipeline_layout) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create pipeline layout.") | |
| } | |
| pipeline_info: vk.GraphicsPipelineCreateInfo | |
| pipeline_info.sType = vk.StructureType.GRAPHICS_PIPELINE_CREATE_INFO | |
| pipeline_info.stageCount = 2 | |
| pipeline_info.pStages = raw_data(shader_stages) | |
| pipeline_info.pVertexInputState = &vertex_input_info | |
| pipeline_info.pInputAssemblyState = &input_assembly_info | |
| pipeline_info.pViewportState = &viewport_state_info | |
| pipeline_info.pRasterizationState = &rasterizer_info | |
| pipeline_info.pMultisampleState = &multisampling_info | |
| pipeline_info.pDepthStencilState = nil | |
| pipeline_info.pColorBlendState = &color_blend_state_info | |
| pipeline_info.pDynamicState = &dynamic_state_info | |
| pipeline_info.layout = pipeline_layout | |
| pipeline_info.renderPass = render_pass | |
| pipeline_info.subpass = 0 | |
| pipeline_info.basePipelineHandle = {} | |
| pipeline_info.basePipelineIndex = -1 | |
| if vk.CreateGraphicsPipelines(logical_device, {}, 1, &pipeline_info, nil, &graphics_pipeline) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create graphics pipeline.") | |
| } | |
| } | |
| read_file_bytes :: proc(file_name: string) -> ([]byte, bool) { | |
| file, file_error := os.open(file_name) | |
| if file_error != os.ERROR_NONE { | |
| fmt.eprintln("Failed to open file.") | |
| return nil, false | |
| } | |
| defer os.close(file) | |
| file_size, _ := os.file_size(file) | |
| buffer := make([]byte, file_size) | |
| os.seek(file, 0, os.SEEK_SET) | |
| bytes_read, bytes_read_error := os.read(file, buffer) | |
| if (bytes_read_error != os.ERROR_NONE) || (bytes_read != int(file_size)) { | |
| fmt.eprintln("Failed to read file.") | |
| return nil, false | |
| } | |
| return buffer, true | |
| } | |
| create_shader_module :: proc(using ctx: ^Context, code: []byte) -> (vk.ShaderModule, bool) { | |
| info: vk.ShaderModuleCreateInfo | |
| info.sType = vk.StructureType.SHADER_MODULE_CREATE_INFO | |
| info.codeSize = len(code) | |
| info.pCode = auto_cast raw_data(code) | |
| shader_module: vk.ShaderModule | |
| if (vk.CreateShaderModule(logical_device, &info, nil, &shader_module) != vk.Result.SUCCESS) { | |
| fmt.eprintln("Failed to create shader module.") | |
| return shader_module, false | |
| } | |
| return shader_module, true | |
| } | |
| create_render_pass :: proc(using ctx: ^Context) { | |
| color_attachment_desc: vk.AttachmentDescription | |
| color_attachment_desc.format = swap_chain_image_format | |
| color_attachment_desc.samples = {vk.SampleCountFlag._1} | |
| color_attachment_desc.loadOp = vk.AttachmentLoadOp.CLEAR | |
| color_attachment_desc.storeOp = vk.AttachmentStoreOp.STORE | |
| color_attachment_desc.stencilLoadOp = vk.AttachmentLoadOp.DONT_CARE | |
| color_attachment_desc.stencilStoreOp = vk.AttachmentStoreOp.DONT_CARE | |
| color_attachment_desc.initialLayout = vk.ImageLayout.UNDEFINED | |
| color_attachment_desc.finalLayout = vk.ImageLayout.PRESENT_SRC_KHR | |
| color_attachment_ref: vk.AttachmentReference | |
| color_attachment_ref.attachment = 0 | |
| color_attachment_ref.layout = vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL | |
| subpass_desc: vk.SubpassDescription | |
| subpass_desc.pipelineBindPoint = vk.PipelineBindPoint.GRAPHICS | |
| subpass_desc.colorAttachmentCount = 1 | |
| subpass_desc.pColorAttachments = &color_attachment_ref | |
| subpass_dependency: vk.SubpassDependency | |
| subpass_dependency.srcSubpass = vk.SUBPASS_EXTERNAL | |
| subpass_dependency.dstSubpass = 0 | |
| subpass_dependency.srcStageMask = {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT} | |
| subpass_dependency.srcAccessMask = {} | |
| subpass_dependency.dstStageMask = {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT} | |
| subpass_dependency.dstAccessMask = {vk.AccessFlag.COLOR_ATTACHMENT_WRITE} | |
| render_pass_info: vk.RenderPassCreateInfo | |
| render_pass_info.sType = vk.StructureType.RENDER_PASS_CREATE_INFO | |
| render_pass_info.attachmentCount = 1 | |
| render_pass_info.pAttachments = &color_attachment_desc | |
| render_pass_info.subpassCount = 1 | |
| render_pass_info.pSubpasses = &subpass_desc | |
| render_pass_info.dependencyCount = 1 | |
| render_pass_info.pDependencies = &subpass_dependency | |
| if vk.CreateRenderPass(logical_device, &render_pass_info, nil, &render_pass) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create render pass.") | |
| } | |
| } | |
| create_framebuffers :: proc(using ctx: ^Context) { | |
| resize(&swap_chain_framebuffers, len(swap_chain_image_views)) | |
| for index in 0 ..< len(swap_chain_image_views) { | |
| attachments: vk.ImageView = swap_chain_image_views[index] | |
| framebuffer_info: vk.FramebufferCreateInfo | |
| framebuffer_info.sType = vk.StructureType.FRAMEBUFFER_CREATE_INFO | |
| framebuffer_info.renderPass = render_pass | |
| framebuffer_info.attachmentCount = 1 | |
| framebuffer_info.pAttachments = &attachments | |
| framebuffer_info.width = swap_chain_extent.width | |
| framebuffer_info.height = swap_chain_extent.height | |
| framebuffer_info.layers = 1 | |
| if vk.CreateFramebuffer(logical_device, &framebuffer_info, nil, &swap_chain_framebuffers[index]) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create framebuffer #", index, ".") | |
| } | |
| } | |
| } | |
| create_command_pool :: proc(using ctx: ^Context) { | |
| queue_family_indices: Queue_Family_Indices = find_queue_families(ctx, physical_device) | |
| command_pool_info: vk.CommandPoolCreateInfo | |
| command_pool_info.sType = vk.StructureType.COMMAND_POOL_CREATE_INFO | |
| command_pool_info.flags = {vk.CommandPoolCreateFlag.RESET_COMMAND_BUFFER} | |
| command_pool_info.queueFamilyIndex = queue_family_indices.graphics_family.(u32) | |
| if vk.CreateCommandPool(logical_device, &command_pool_info, nil, &command_pool) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create command pool.") | |
| } | |
| } | |
| create_command_buffer :: proc(using ctx: ^Context) { | |
| commannd_buffer_allocate_info: vk.CommandBufferAllocateInfo | |
| commannd_buffer_allocate_info.sType = vk.StructureType.COMMAND_BUFFER_ALLOCATE_INFO | |
| commannd_buffer_allocate_info.commandPool = command_pool | |
| commannd_buffer_allocate_info.level = vk.CommandBufferLevel.PRIMARY | |
| commannd_buffer_allocate_info.commandBufferCount = 1 | |
| if vk.AllocateCommandBuffers(logical_device, &commannd_buffer_allocate_info, &command_buffer) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to allocated command buffers.") | |
| } | |
| } | |
| record_command_buffer :: proc(using ctx: ^Context, image_index: u32) { | |
| command_buffer_begin_info: vk.CommandBufferBeginInfo | |
| command_buffer_begin_info.sType = vk.StructureType.COMMAND_BUFFER_BEGIN_INFO | |
| command_buffer_begin_info.flags = {} | |
| command_buffer_begin_info.pInheritanceInfo = nil | |
| if vk.BeginCommandBuffer(command_buffer, &command_buffer_begin_info) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to begin recording command buffer.") | |
| } | |
| render_pass_begin_info: vk.RenderPassBeginInfo | |
| render_pass_begin_info.sType = vk.StructureType.RENDER_PASS_BEGIN_INFO | |
| render_pass_begin_info.renderPass = render_pass | |
| render_pass_begin_info.framebuffer = swap_chain_framebuffers[image_index] | |
| render_pass_begin_info.renderArea.offset = {0, 0} | |
| render_pass_begin_info.renderArea.extent = swap_chain_extent | |
| clear_color: vk.ClearValue = { | |
| color = {float32 = {0.0, 0.0, 0.0, 1.0}}, | |
| } | |
| render_pass_begin_info.clearValueCount = 1 | |
| render_pass_begin_info.pClearValues = &clear_color | |
| vk.CmdBeginRenderPass(command_buffer, &render_pass_begin_info, vk.SubpassContents.INLINE) | |
| vk.CmdBindPipeline(command_buffer, vk.PipelineBindPoint.GRAPHICS, graphics_pipeline) | |
| viewport: vk.Viewport | |
| viewport.x = 0.0 | |
| viewport.y = 0.0 | |
| viewport.width = f32(swap_chain_extent.width) | |
| viewport.height = f32(swap_chain_extent.height) | |
| viewport.minDepth = 0.0 | |
| viewport.maxDepth = 1.0 | |
| vk.CmdSetViewport(command_buffer, 0, 1, &viewport) | |
| scissor: vk.Rect2D | |
| scissor.offset = {0, 0} | |
| scissor.extent = swap_chain_extent | |
| vk.CmdSetScissor(command_buffer, 0, 1, &scissor) | |
| vk.CmdDraw(command_buffer, 3, 1, 0, 0) | |
| vk.CmdEndRenderPass(command_buffer) | |
| if vk.EndCommandBuffer(command_buffer) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to record command buffer.") | |
| } | |
| } | |
| draw_frame :: proc(using ctx: ^Context) { | |
| vk.WaitForFences(logical_device, 1, &in_flight_fence, true, math.max(u64)) | |
| vk.ResetFences(logical_device, 1, &in_flight_fence) | |
| image_index: u32 | |
| vk.AcquireNextImageKHR(logical_device, swap_chain, max(u64), image_available_semaphore, {}, &image_index) | |
| vk.ResetCommandBuffer(command_buffer, {}) | |
| record_command_buffer(ctx, image_index) | |
| submit_info: vk.SubmitInfo | |
| submit_info.sType = vk.StructureType.SUBMIT_INFO | |
| wait_semaphores: []vk.Semaphore = {image_available_semaphore} | |
| wait_stages: []vk.PipelineStageFlags = {{vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT}} | |
| submit_info.waitSemaphoreCount = 1 | |
| submit_info.pWaitSemaphores = raw_data(wait_semaphores) | |
| submit_info.pWaitDstStageMask = raw_data(wait_stages) | |
| submit_info.commandBufferCount = 1 | |
| submit_info.pCommandBuffers = &command_buffer | |
| signal_semaphores: []vk.Semaphore = {render_finished_sempahore} | |
| submit_info.signalSemaphoreCount = 1 | |
| submit_info.pSignalSemaphores = raw_data(signal_semaphores) | |
| if vk.QueueSubmit(graphics_queue, 1, &submit_info, in_flight_fence) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to submit draw command buffer.") | |
| } | |
| present_info: vk.PresentInfoKHR | |
| present_info.sType = vk.StructureType.PRESENT_INFO_KHR | |
| present_info.waitSemaphoreCount = 1 | |
| present_info.pWaitSemaphores = raw_data(signal_semaphores) | |
| swap_chains: []vk.SwapchainKHR = {swap_chain} | |
| present_info.swapchainCount = 1 | |
| present_info.pSwapchains = raw_data(swap_chains) | |
| present_info.pImageIndices = &image_index | |
| present_info.pResults = nil | |
| vk.QueuePresentKHR(present_queue, &present_info) | |
| } | |
| create_sync_objects :: proc(using ctx: ^Context) { | |
| semaphore_info: vk.SemaphoreCreateInfo | |
| semaphore_info.sType = vk.StructureType.SEMAPHORE_CREATE_INFO | |
| fence_info: vk.FenceCreateInfo | |
| fence_info.sType = vk.StructureType.FENCE_CREATE_INFO | |
| fence_info.flags = {vk.FenceCreateFlag.SIGNALED} | |
| if vk.CreateSemaphore(logical_device, &semaphore_info, nil, &image_available_semaphore) != vk.Result.SUCCESS || | |
| vk.CreateSemaphore(logical_device, &semaphore_info, nil, &render_finished_sempahore) != vk.Result.SUCCESS || | |
| vk.CreateFence(logical_device, &fence_info, nil, &in_flight_fence) != vk.Result.SUCCESS { | |
| fmt.eprintln("Failed to create sempahores and fences.") | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment