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