Skip to content

Instantly share code, notes, and snippets.

@jockus
Last active July 29, 2024 21:03

Revisions

  1. jockus revised this gist Jun 27, 2024. 1 changed file with 6 additions and 14 deletions.
    20 changes: 6 additions & 14 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,7 @@
    package vulkan_tutorial

    import "base:runtime"
    import "core:fmt"
    import "core:c"
    import "core:dynlib"
    import "core:strings"
    import "core:mem"
    import "vendor:glfw"
    import vk "vendor:vulkan"
    @@ -130,14 +127,14 @@ instance_create :: proc() -> (instance: Instance) {
    defer delete(supported_layers)
    vkok(vk.EnumerateInstanceLayerProperties(&supported_layer_count, raw_data(supported_layers)))
    for required_layer in required_layers {
    found : bool
    layer_found : bool
    for &supported_layer in supported_layers {
    if required_layer == cstring(&supported_layer.layerName[0]) {
    found = true
    layer_found = true
    break
    }
    }
    if !found {
    if !layer_found {
    fmt.println(ERROR, "A required layer:", required_layer, "is missing!")
    assert(false)
    }
    @@ -155,14 +152,14 @@ instance_create :: proc() -> (instance: Instance) {
    defer delete(supported_extensions)
    vkok(vk.EnumerateInstanceExtensionProperties(nil, &supported_extension_count, raw_data(supported_extensions)))
    for required_extension in required_extensions {
    found : bool
    extension_found : bool
    for &supported_extension in supported_extensions {
    if required_extension == cstring(&supported_extension.extensionName[0]) {
    found = true
    extension_found = true
    break
    }
    }
    if !found {
    if !extension_found {
    fmt.println(ERROR, "A required extension:", required_extension, "is missing!")
    assert(false)
    }
    @@ -231,12 +228,8 @@ device_create :: proc(instance : Instance, surface : Surface) -> (device: Device

    at_least_one_pdevice_okay : bool
    okay_pdevice : vk.PhysicalDevice
    pdevice_properties : vk.PhysicalDeviceProperties
    graphics_family_queue_index : u32
    for pdevice in physical_devices {
    pd_properties : vk.PhysicalDeviceProperties
    vk.GetPhysicalDeviceProperties(pdevice, &pd_properties)

    pdevice_supported_extensions_count : u32
    vkok(vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, nil))
    pdevice_supported_extensions := make([]vk.ExtensionProperties, pdevice_supported_extensions_count)
    @@ -279,7 +272,6 @@ device_create :: proc(instance : Instance, surface : Surface) -> (device: Device
    if has_required_extensions && has_required_queue_family {
    at_least_one_pdevice_okay = true
    okay_pdevice = pdevice
    pdevice_properties = pd_properties
    }
    }

  2. jockus revised this gist Jun 27, 2024. 1 changed file with 8 additions and 17 deletions.
    25 changes: 8 additions & 17 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -75,7 +75,6 @@ main :: proc() {
    glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
    glfw.WindowHint(glfw.RESIZABLE, glfw.TRUE)
    glfw.WindowHint(glfw.VISIBLE, glfw.FALSE);

    window_handle := glfw.CreateWindow(1000, 1000, "Vulkan Window", nil, nil)
    assert(window_handle != nil)
    defer glfw.DestroyWindow(window_handle)
    @@ -117,15 +116,6 @@ instance_create :: proc() -> (instance: Instance) {

    vk.load_proc_addresses_global(vkGetInstanceProcAddr)

    appinfo := vk.ApplicationInfo {
    sType = vk.StructureType.APPLICATION_INFO,
    pApplicationName = "Minimal Modern Vulkan",
    applicationVersion = vk.MAKE_VERSION(1,0,0),
    pEngineName = "Minimal Modern Vulkan",
    engineVersion = vk.MAKE_VERSION(1,0,0),
    apiVersion = vk.API_VERSION_1_3,
    }

    required_layers : [dynamic] cstring
    defer delete(required_layers)
    when ODIN_DEBUG {
    @@ -178,6 +168,14 @@ instance_create :: proc() -> (instance: Instance) {
    }
    }

    appinfo := vk.ApplicationInfo {
    sType = vk.StructureType.APPLICATION_INFO,
    pApplicationName = "Minimal Modern Vulkan",
    applicationVersion = vk.MAKE_VERSION(1,0,0),
    pEngineName = "Minimal Modern Vulkan",
    engineVersion = vk.MAKE_VERSION(1,0,0),
    apiVersion = vk.API_VERSION_1_3,
    }
    icreateinfo := vk.InstanceCreateInfo {
    sType = vk.StructureType.INSTANCE_CREATE_INFO,
    pApplicationInfo = &appinfo,
    @@ -186,7 +184,6 @@ instance_create :: proc() -> (instance: Instance) {
    enabledExtensionCount = u32(len(required_extensions)),
    ppEnabledExtensionNames = raw_data(required_extensions),
    }

    vkok(vk.CreateInstance(&icreateinfo, nil, &instance.instance))

    vk.load_proc_addresses(instance.instance)
    @@ -300,7 +297,6 @@ device_create :: proc(instance : Instance, surface : Surface) -> (device: Device
    queueCount = 1.0,
    pQueuePriorities = &queue_priority_graphics,
    }

    enabledShaderObjectFeaturesEXT := vk.PhysicalDeviceShaderObjectFeaturesEXT{
    sType = vk.StructureType.PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT,
    shaderObject = true,
    @@ -311,7 +307,6 @@ device_create :: proc(instance : Instance, surface : Surface) -> (device: Device
    pNext = &enabledShaderObjectFeaturesEXT
    }
    device_features : vk.PhysicalDeviceFeatures

    dci := vk.DeviceCreateInfo {
    sType = vk.StructureType.DEVICE_CREATE_INFO,
    queueCreateInfoCount = 1,
    @@ -321,7 +316,6 @@ device_create :: proc(instance : Instance, surface : Surface) -> (device: Device
    ppEnabledExtensionNames = raw_data(pdevice_required_extensions),
    pNext = &dynamic_rendering_feature,
    }

    vkok(vk.CreateDevice(device.physical_device, &dci, nil, &device.device))

    vk.GetDeviceQueue(device.device, device.graphics_family_queue_index, 0, &device.graphics_queue)
    @@ -566,7 +560,6 @@ record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, ima
    cbbi := vk.CommandBufferBeginInfo {
    sType = .COMMAND_BUFFER_BEGIN_INFO
    }

    vkok(vk.BeginCommandBuffer(rendering.command_buffer, &cbbi))

    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], true)
    @@ -582,7 +575,6 @@ record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, ima
    storeOp = vk.AttachmentStoreOp.STORE,
    clearValue = clear_color,
    }

    rendering_info := vk.RenderingInfoKHR{
    sType = vk.StructureType.RENDERING_INFO_KHR,
    renderArea = {
    @@ -593,7 +585,6 @@ record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, ima
    colorAttachmentCount = 1,
    pColorAttachments = &color_attachment_info,
    }

    vk.CmdBeginRenderingKHR(rendering.command_buffer, &rendering_info)

    // Set pipeline state
  3. jockus revised this gist Jun 27, 2024. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -201,6 +201,7 @@ instance_destroy :: proc(instance : ^Instance) {
    Surface :: struct {
    surface : vk.SurfaceKHR
    }

    surface_create :: proc(instance : Instance, window_handle : glfw.WindowHandle) -> (surface: Surface) {
    vkok(glfw.CreateWindowSurface(instance.instance, window_handle, nil, &surface.surface))
    return
  4. jockus revised this gist Jun 27, 2024. 1 changed file with 65 additions and 54 deletions.
    119 changes: 65 additions & 54 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -17,38 +17,6 @@ WARNING :: "WARNING:"
    vertex_shader_bytecode :: #load("./shaders/vertex.spv")
    fragment_shader_bytecode :: #load("./shaders/fragment.spv")

    Instance :: struct {
    instance : vk.Instance
    }

    Surface :: struct {
    surface : vk.SurfaceKHR
    }

    Device :: struct {
    physical_device : vk.PhysicalDevice,
    device : vk.Device,
    graphics_queue : vk.Queue,
    graphics_family_queue_index : u32,
    }

    Swapchain :: struct {
    swapchain : vk.SwapchainKHR,
    images : []vk.Image,
    image_views : []vk.ImageView,
    extent : vk.Extent2D,
    format : vk.Format,
    }

    Rendering :: struct {
    shaders : [2]vk.ShaderEXT,
    command_pool : vk.CommandPool,
    command_buffer : vk.CommandBuffer,
    image_available_semaphore : vk.Semaphore,
    render_finished_semaphore : vk.Semaphore,
    in_flight_fence : vk.Fence,
    }

    vkok :: proc(result : vk.Result, loc := #caller_location) {
    #partial switch result {
    case .SUCCESS:
    @@ -112,16 +80,16 @@ main :: proc() {
    assert(window_handle != nil)
    defer glfw.DestroyWindow(window_handle)

    instance := init_vulkan_instance()
    defer vk.DestroyInstance(instance.instance, nil)
    surface := init_vulkan_surface(instance, window_handle)
    defer vk.DestroySurfaceKHR(instance.instance, surface.surface, nil)
    device := init_vulkan_device(instance, surface)
    defer vk.DestroyDevice(device.device, nil)
    swapchain := init_vulkan_swapchain(device, surface)
    defer destroy_swapchain(device, &swapchain)
    rendering := init_rendering(device)
    defer destroy_rendering(device, &rendering)
    instance := instance_create()
    defer instance_destroy(&instance)
    surface := surface_create(instance, window_handle)
    defer surface_destroy(instance, &surface)
    device := device_create(instance, surface)
    defer device_destroy(&device)
    swapchain := swapchain_create(device, surface)
    defer swapchain_destroy(device, &swapchain)
    rendering := rendering_create(device)
    defer rendering_destroy(device, &rendering)
    defer vk.DeviceWaitIdle(device.device)

    first_present := true
    @@ -136,7 +104,11 @@ main :: proc() {

    }

    init_vulkan_instance :: proc() -> (instance: Instance) {
    Instance :: struct {
    instance : vk.Instance
    }

    instance_create :: proc() -> (instance: Instance) {
    vulkan_lib, loaded := dynlib.load_library("vulkan-1.dll")
    assert(loaded)

    @@ -222,12 +194,30 @@ init_vulkan_instance :: proc() -> (instance: Instance) {
    return
    }

    init_vulkan_surface :: proc(instance : Instance, window_handle : glfw.WindowHandle) -> (surface: Surface) {
    instance_destroy :: proc(instance : ^Instance) {
    vk.DestroyInstance(instance.instance, nil)
    }

    Surface :: struct {
    surface : vk.SurfaceKHR
    }
    surface_create :: proc(instance : Instance, window_handle : glfw.WindowHandle) -> (surface: Surface) {
    vkok(glfw.CreateWindowSurface(instance.instance, window_handle, nil, &surface.surface))
    return
    }

    init_vulkan_device :: proc(instance : Instance, surface : Surface) -> (device: Device) {
    surface_destroy :: proc(instance : Instance, surface : ^Surface) {
    vk.DestroySurfaceKHR(instance.instance, surface.surface, nil)
    }

    Device :: struct {
    physical_device : vk.PhysicalDevice,
    device : vk.Device,
    graphics_queue : vk.Queue,
    graphics_family_queue_index : u32,
    }

    device_create :: proc(instance : Instance, surface : Surface) -> (device: Device) {
    pds_count : u32
    vkok(vk.EnumeratePhysicalDevices(instance.instance, &pds_count, nil))
    physical_devices := make([]vk.PhysicalDevice, pds_count)
    @@ -338,7 +328,19 @@ init_vulkan_device :: proc(instance : Instance, surface : Surface) -> (device: D
    return
    }

    init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> (swapchain: Swapchain) {
    device_destroy :: proc(device : ^Device) {
    vk.DestroyDevice(device.device, nil)
    }

    Swapchain :: struct {
    swapchain : vk.SwapchainKHR,
    images : []vk.Image,
    image_views : []vk.ImageView,
    extent : vk.Extent2D,
    format : vk.Format,
    }

    swapchain_create :: proc(device : Device, surface : Surface) -> (swapchain: Swapchain) {
    supported_surface_formats_count : u32
    vkok(vk.GetPhysicalDeviceSurfaceFormatsKHR(device.physical_device, surface.surface, &supported_surface_formats_count, nil))
    supported_surface_formats := make([]vk.SurfaceFormatKHR, supported_surface_formats_count)
    @@ -428,7 +430,7 @@ init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> (swapchain:
    return
    }

    destroy_swapchain :: proc(device : Device, swapchain : ^Swapchain) {
    swapchain_destroy :: proc(device : Device, swapchain : ^Swapchain) {
    for image_view in swapchain.image_views {
    vk.DestroyImageView(device.device, image_view, nil);
    }
    @@ -437,13 +439,22 @@ destroy_swapchain :: proc(device : Device, swapchain : ^Swapchain) {
    vk.DestroySwapchainKHR(device.device, swapchain.swapchain, nil);
    }

    recreate_swapchain :: proc(device : Device, surface : Surface, swapchain : ^Swapchain) {
    swapchain_recreate :: proc(device : Device, surface : Surface, swapchain : ^Swapchain) {
    vk.DeviceWaitIdle(device.device)
    destroy_swapchain(device, swapchain)
    swapchain^ = init_vulkan_swapchain(device, surface)
    swapchain_destroy(device, swapchain)
    swapchain^ = swapchain_create(device, surface)
    }

    Rendering :: struct {
    shaders : [2]vk.ShaderEXT,
    command_pool : vk.CommandPool,
    command_buffer : vk.CommandBuffer,
    image_available_semaphore : vk.Semaphore,
    render_finished_semaphore : vk.Semaphore,
    in_flight_fence : vk.Fence,
    }

    init_rendering :: proc(device : Device) -> (rendering: Rendering) {
    rendering_create :: proc(device : Device) -> (rendering: Rendering) {
    // Ensure source is 4 bytes aligned
    vertex, _ := mem.alloc_bytes(len(vertex_shader_bytecode), 4)
    mem.copy(raw_data(vertex), raw_data(vertex_shader_bytecode), len(vertex_shader_bytecode))
    @@ -509,7 +520,7 @@ init_rendering :: proc(device : Device) -> (rendering: Rendering) {
    return
    }

    destroy_rendering :: proc(device : Device, rendering : ^Rendering) {
    rendering_destroy :: proc(device : Device, rendering : ^Rendering) {
    vk.DestroyShaderEXT(device.device, rendering.shaders[0], nil)
    vk.DestroyShaderEXT(device.device, rendering.shaders[1], nil)
    vk.FreeCommandBuffers(device.device, rendering.command_pool, 1, &rendering.command_buffer)
    @@ -638,7 +649,7 @@ render_frame :: proc(device : Device, surface : Surface, swapchain : ^Swapchain,
    image_index : u32
    acquire_result := vk.AcquireNextImageKHR(device.device, swapchain.swapchain, max(u64), rendering.image_available_semaphore, 0, &image_index)
    if acquire_result == vk.Result.ERROR_OUT_OF_DATE_KHR || acquire_result == vk.Result.SUBOPTIMAL_KHR {
    recreate_swapchain(device, surface, swapchain)
    swapchain_recreate(device, surface, swapchain)
    return
    }
    else {
    @@ -672,7 +683,7 @@ render_frame :: proc(device : Device, surface : Surface, swapchain : ^Swapchain,
    }
    present_result := vk.QueuePresentKHR(device.graphics_queue, &present_info)
    if present_result == vk.Result.ERROR_OUT_OF_DATE_KHR || present_result == vk.Result.SUBOPTIMAL_KHR {
    recreate_swapchain(device, surface, swapchain)
    swapchain_recreate(device, surface, swapchain)
    }
    else {
    vkok(present_result)
  5. jockus revised this gist Jun 27, 2024. 1 changed file with 50 additions and 60 deletions.
    110 changes: 50 additions & 60 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -136,7 +136,7 @@ main :: proc() {

    }

    init_vulkan_instance :: proc() -> Instance {
    init_vulkan_instance :: proc() -> (instance: Instance) {
    vulkan_lib, loaded := dynlib.load_library("vulkan-1.dll")
    assert(loaded)

    @@ -215,22 +215,20 @@ init_vulkan_instance :: proc() -> Instance {
    ppEnabledExtensionNames = raw_data(required_extensions),
    }

    instance : vk.Instance
    vkok(vk.CreateInstance(&icreateinfo, nil, &instance))
    vkok(vk.CreateInstance(&icreateinfo, nil, &instance.instance))

    vk.load_proc_addresses(instance)
    vk.load_proc_addresses(instance.instance)

    return Instance{instance}
    return
    }

    init_vulkan_surface :: proc(instance : Instance, window_handle : glfw.WindowHandle) -> Surface {
    surface : vk.SurfaceKHR
    vkok(glfw.CreateWindowSurface(instance.instance, window_handle, nil, &surface))
    return Surface{surface}
    init_vulkan_surface :: proc(instance : Instance, window_handle : glfw.WindowHandle) -> (surface: Surface) {
    vkok(glfw.CreateWindowSurface(instance.instance, window_handle, nil, &surface.surface))
    return
    }

    init_vulkan_device :: proc(instance : Instance, surface : Surface) -> Device {
    pds_count : u32
    init_vulkan_device :: proc(instance : Instance, surface : Surface) -> (device: Device) {
    pds_count : u32
    vkok(vk.EnumeratePhysicalDevices(instance.instance, &pds_count, nil))
    physical_devices := make([]vk.PhysicalDevice, pds_count)
    defer delete(physical_devices)
    @@ -301,8 +299,8 @@ init_vulkan_device :: proc(instance : Instance, surface : Surface) -> Device {
    fmt.eprintln("No physical devices have all the required extensions")
    assert(false)
    }
    pdevice : vk.PhysicalDevice
    pdevice = okay_pdevice
    device.physical_device = okay_pdevice
    device.graphics_family_queue_index = graphics_family_queue_index

    queue_priority_graphics : f32 = 1
    qci_graphics := vk.DeviceQueueCreateInfo {
    @@ -333,16 +331,14 @@ init_vulkan_device :: proc(instance : Instance, surface : Surface) -> Device {
    pNext = &dynamic_rendering_feature,
    }

    device : vk.Device
    vkok(vk.CreateDevice(pdevice, &dci, nil, &device))
    vkok(vk.CreateDevice(device.physical_device, &dci, nil, &device.device))

    graphics_queue : vk.Queue
    vk.GetDeviceQueue(device, graphics_family_queue_index, 0, &graphics_queue)
    vk.GetDeviceQueue(device.device, device.graphics_family_queue_index, 0, &device.graphics_queue)

    return Device{pdevice, device, graphics_queue, graphics_family_queue_index}
    return
    }

    init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {
    init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> (swapchain: Swapchain) {
    supported_surface_formats_count : u32
    vkok(vk.GetPhysicalDeviceSurfaceFormatsKHR(device.physical_device, surface.surface, &supported_surface_formats_count, nil))
    supported_surface_formats := make([]vk.SurfaceFormatKHR, supported_surface_formats_count)
    @@ -378,9 +374,9 @@ init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {
    vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(device.physical_device, surface.surface, &surface_capabilities)
    surface_image_count := min(surface_capabilities.minImageCount + 1, surface_capabilities.maxImageCount)
    extent_special_value := vk.Extent2D{max(u32), max(u32)}
    extent := surface_capabilities.currentExtent
    if extent == extent_special_value {
    extent = surface_capabilities.minImageExtent
    swapchain.extent = surface_capabilities.currentExtent
    if swapchain.extent == extent_special_value {
    swapchain.extent = surface_capabilities.minImageExtent
    }

    swpcnci := vk.SwapchainCreateInfoKHR {
    @@ -389,7 +385,7 @@ init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {
    minImageCount = surface_image_count,
    imageFormat = surface_format.format,
    imageColorSpace = surface_format.colorSpace,
    imageExtent = extent,
    imageExtent = swapchain.extent,
    imageArrayLayers = 1,
    imageUsage = { vk.ImageUsageFlag.COLOR_ATTACHMENT },
    imageSharingMode = .EXCLUSIVE,
    @@ -399,19 +395,18 @@ init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {
    clipped = true,
    }

    swapchain : vk.SwapchainKHR
    vkok(vk.CreateSwapchainKHR(device.device, &swpcnci, nil, &swapchain))
    vkok(vk.CreateSwapchainKHR(device.device, &swpcnci, nil, &swapchain.swapchain))

    device_image_count : u32
    vkok(vk.GetSwapchainImagesKHR(device.device, swapchain, &device_image_count, nil))
    swapchain_images := make([]vk.Image, device_image_count)
    vkok(vk.GetSwapchainImagesKHR(device.device, swapchain, &device_image_count, raw_data(swapchain_images)))
    vkok(vk.GetSwapchainImagesKHR(device.device, swapchain.swapchain, &device_image_count, nil))
    swapchain.images = make([]vk.Image, device_image_count)
    vkok(vk.GetSwapchainImagesKHR(device.device, swapchain.swapchain, &device_image_count, raw_data(swapchain.images)))

    swapchain_imageviews := make([]vk.ImageView, device_image_count)
    swapchain.image_views = make([]vk.ImageView, device_image_count)
    for i in 0..<device_image_count {
    imvci := vk.ImageViewCreateInfo {
    sType = .IMAGE_VIEW_CREATE_INFO,
    image = swapchain_images[i],
    image = swapchain.images[i],
    viewType = vk.ImageViewType.D2,
    format = surface_format.format,
    components = {
    @@ -421,16 +416,16 @@ init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {
    a = .IDENTITY,
    },
    subresourceRange = {
    aspectMask = { .COLOR },
    baseMipLevel = 0,
    levelCount = 1,
    aspectMask = { .COLOR },
    baseMipLevel = 0,
    levelCount = 1,
    baseArrayLayer = 0,
    layerCount = 1,
    layerCount = 1,
    },
    }
    vkok(vk.CreateImageView(device.device, &imvci, nil, &swapchain_imageviews[i]))
    vkok(vk.CreateImageView(device.device, &imvci, nil, &swapchain.image_views[i]))
    }
    return Swapchain{swapchain, swapchain_images, swapchain_imageviews, extent, surface_format.format}
    return
    }

    destroy_swapchain :: proc(device : Device, swapchain : ^Swapchain) {
    @@ -448,7 +443,7 @@ recreate_swapchain :: proc(device : Device, surface : Surface, swapchain : ^Swap
    swapchain^ = init_vulkan_swapchain(device, surface)
    }

    init_rendering :: proc(device : Device) -> Rendering {
    init_rendering :: proc(device : Device) -> (rendering: Rendering) {
    // Ensure source is 4 bytes aligned
    vertex, _ := mem.alloc_bytes(len(vertex_shader_bytecode), 4)
    mem.copy(raw_data(vertex), raw_data(vertex_shader_bytecode), len(vertex_shader_bytecode))
    @@ -483,41 +478,35 @@ init_rendering :: proc(device : Device) -> Rendering {
    // pSetLayouts = &descriptorSetLayout,
    }
    }
    shaders : [2]vk.ShaderEXT
    vkok(vk.CreateShadersEXT(device.device, 2, &shaderCreateInfos[0], nil, &shaders[0]))
    vkok(vk.CreateShadersEXT(device.device, 2, &shaderCreateInfos[0], nil, &rendering.shaders[0]))

    cpci := vk.CommandPoolCreateInfo {
    sType = .COMMAND_POOL_CREATE_INFO,
    flags = { vk.CommandPoolCreateFlags.RESET_COMMAND_BUFFER },
    sType = .COMMAND_POOL_CREATE_INFO,
    flags = { vk.CommandPoolCreateFlags.RESET_COMMAND_BUFFER },
    queueFamilyIndex = device.graphics_family_queue_index,
    }
    command_pool : vk.CommandPool
    vkok(vk.CreateCommandPool(device.device, &cpci, nil, &command_pool))
    vkok(vk.CreateCommandPool(device.device, &cpci, nil, &rendering.command_pool))

    cbai := vk.CommandBufferAllocateInfo {
    sType = .COMMAND_BUFFER_ALLOCATE_INFO,
    commandPool = command_pool,
    level = .PRIMARY,
    sType = .COMMAND_BUFFER_ALLOCATE_INFO,
    commandPool = rendering.command_pool,
    level = .PRIMARY,
    commandBufferCount = 1,
    }
    command_buffer : vk.CommandBuffer
    vkok(vk.AllocateCommandBuffers(device.device, &cbai, &command_buffer))
    vkok(vk.AllocateCommandBuffers(device.device, &cbai, &rendering.command_buffer))

    sci := vk.SemaphoreCreateInfo {
    sType = .SEMAPHORE_CREATE_INFO
    }
    image_available_semaphore : vk.Semaphore
    vkok(vk.CreateSemaphore(device.device, &sci, nil, &image_available_semaphore))
    render_finished_semaphore : vk.Semaphore
    vkok(vk.CreateSemaphore(device.device, &sci, nil, &render_finished_semaphore))
    vkok(vk.CreateSemaphore(device.device, &sci, nil, &rendering.image_available_semaphore))
    vkok(vk.CreateSemaphore(device.device, &sci, nil, &rendering.render_finished_semaphore))

    fci := vk.FenceCreateInfo {
    sType = .FENCE_CREATE_INFO,
    flags = { .SIGNALED },
    }
    in_flight_fence : vk.Fence
    vkok(vk.CreateFence(device.device, &fci, nil, &in_flight_fence))
    return Rendering{shaders, command_pool, command_buffer, image_available_semaphore, render_finished_semaphore, in_flight_fence}
    vkok(vk.CreateFence(device.device, &fci, nil, &rendering.in_flight_fence))
    return
    }

    destroy_rendering :: proc(device : Device, rendering : ^Rendering) {
    @@ -562,15 +551,16 @@ swapchain_barrier :: proc(command_buffer : vk.CommandBuffer, image : vk.Image, t
    }

    record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, image_index : u32) {
    cbbi : vk.CommandBufferBeginInfo
    cbbi.sType = .COMMAND_BUFFER_BEGIN_INFO
    cbbi := vk.CommandBufferBeginInfo {
    sType = .COMMAND_BUFFER_BEGIN_INFO
    }

    vkok(vk.BeginCommandBuffer(rendering.command_buffer, &cbbi))

    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], true)

    clear_color := vk.ClearValue{
    color = { float32 = {0,0,0,1}}
    color = {float32 = {0,0,0,1}}
    }
    color_attachment_info := vk.RenderingAttachmentInfoKHR{
    sType = vk.StructureType.RENDERING_ATTACHMENT_INFO_KHR,
    @@ -621,10 +611,10 @@ record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, ima
    colorBlendEquations := vk.ColorBlendEquationEXT{
    srcColorBlendFactor = vk.BlendFactor.SRC_ALPHA,
    dstColorBlendFactor = .ONE_MINUS_SRC_ALPHA,
    colorBlendOp = .ADD,
    colorBlendOp = .ADD,
    srcAlphaBlendFactor = .ONE,
    dstAlphaBlendFactor = .ZERO,
    alphaBlendOp = .ADD,
    alphaBlendOp = .ADD,
    }
    vk.CmdSetColorBlendEquationEXT(rendering.command_buffer, 0, 1, &colorBlendEquations)

  6. jockus revised this gist Jun 27, 2024. 1 changed file with 28 additions and 0 deletions.
    28 changes: 28 additions & 0 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -77,6 +77,30 @@ vkok :: proc(result : vk.Result, loc := #caller_location) {
    }

    main :: proc() {
    when ODIN_DEBUG {
    track: mem.Tracking_Allocator
    mem.tracking_allocator_init(&track, context.allocator)
    context.allocator = mem.tracking_allocator(&track)

    defer {
    if len(track.allocation_map) > 0 {
    fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map))
    for _, entry in track.allocation_map {
    fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
    }
    }
    if len(track.bad_free_array) > 0 {
    fmt.eprintf("=== %v incorrect frees: ===\n", len(track.bad_free_array))
    for entry in track.bad_free_array {
    fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
    }
    }
    mem.tracking_allocator_destroy(&track)
    }
    }



    glfw.Init()
    defer glfw.Terminate()

    @@ -159,6 +183,7 @@ init_vulkan_instance :: proc() -> Instance {

    // Ensure extensions
    required_extensions : [dynamic]cstring
    defer delete(required_extensions)
    for ext in glfw.GetRequiredInstanceExtensions() {
    append(&required_extensions, ext)
    }
    @@ -229,6 +254,7 @@ init_vulkan_device :: proc(instance : Instance, surface : Surface) -> Device {
    pdevice_supported_extensions_count : u32
    vkok(vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, nil))
    pdevice_supported_extensions := make([]vk.ExtensionProperties, pdevice_supported_extensions_count)
    defer delete(pdevice_supported_extensions)
    vkok(vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, raw_data(pdevice_supported_extensions)))

    has_required_extensions := true
    @@ -411,6 +437,8 @@ destroy_swapchain :: proc(device : Device, swapchain : ^Swapchain) {
    for image_view in swapchain.image_views {
    vk.DestroyImageView(device.device, image_view, nil);
    }
    delete(swapchain.image_views)
    delete(swapchain.images)
    vk.DestroySwapchainKHR(device.device, swapchain.swapchain, nil);
    }

  7. jockus revised this gist Jun 27, 2024. 1 changed file with 159 additions and 160 deletions.
    319 changes: 159 additions & 160 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -50,65 +50,65 @@ Rendering :: struct {
    }

    vkok :: proc(result : vk.Result, loc := #caller_location) {
    #partial switch result {
    case .SUCCESS:
    case .ERROR_OUT_OF_HOST_MEMORY:
    fmt.eprintln(ERROR, "Out of host memory.")
    case .ERROR_OUT_OF_DEVICE_MEMORY:
    fmt.eprintln(ERROR, "Out of device memory.")
    case .ERROR_INITIALIZATION_FAILED:
    fmt.eprintln(ERROR, "Initialization failed.")
    case .ERROR_LAYER_NOT_PRESENT:
    fmt.eprintln(ERROR, "Layer not present.")
    case .ERROR_EXTENSION_NOT_PRESENT:
    fmt.eprintln(ERROR, "Extension not present.")
    case .ERROR_INCOMPATIBLE_DRIVER:
    fmt.eprintln(ERROR, "Incompatible driver.")
    case:
    fmt.eprintln(ERROR, "Other Spec 1.3 Error!")
    }
    if result != .SUCCESS {
    fmt.println(ERROR, "vk function result was not .SUCCESS")
    fmt.println( "Result was instead:", result)
    fmt.println(" Error at:")
    fmt.println(" ", loc)
    assert(false)
    }
    #partial switch result {
    case .SUCCESS:
    case .ERROR_OUT_OF_HOST_MEMORY:
    fmt.eprintln(ERROR, "Out of host memory.")
    case .ERROR_OUT_OF_DEVICE_MEMORY:
    fmt.eprintln(ERROR, "Out of device memory.")
    case .ERROR_INITIALIZATION_FAILED:
    fmt.eprintln(ERROR, "Initialization failed.")
    case .ERROR_LAYER_NOT_PRESENT:
    fmt.eprintln(ERROR, "Layer not present.")
    case .ERROR_EXTENSION_NOT_PRESENT:
    fmt.eprintln(ERROR, "Extension not present.")
    case .ERROR_INCOMPATIBLE_DRIVER:
    fmt.eprintln(ERROR, "Incompatible driver.")
    case:
    fmt.eprintln(ERROR, "Other Spec 1.3 Error!")
    }
    if result != .SUCCESS {
    fmt.println(ERROR, "vk function result was not .SUCCESS")
    fmt.println( "Result was instead:", result)
    fmt.println(" Error at:")
    fmt.println(" ", loc)
    assert(false)
    }
    }

    main :: proc() {
    glfw.Init()
    defer glfw.Terminate()

    glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
    glfw.WindowHint(glfw.RESIZABLE, glfw.TRUE)
    glfw.WindowHint(glfw.VISIBLE, glfw.FALSE);

    window_handle := glfw.CreateWindow(1000, 1000, "Vulkan Window", nil, nil)
    assert(window_handle != nil)
    defer glfw.DestroyWindow(window_handle)

    instance := init_vulkan_instance()
    defer vk.DestroyInstance(instance.instance, nil)
    surface := init_vulkan_surface(instance, window_handle)
    defer vk.DestroySurfaceKHR(instance.instance, surface.surface, nil)
    device := init_vulkan_device(instance, surface)
    defer vk.DestroyDevice(device.device, nil)
    swapchain := init_vulkan_swapchain(device, surface)
    defer destroy_swapchain(device, &swapchain)
    rendering := init_rendering(device)
    defer destroy_rendering(device, &rendering)
    defer vk.DeviceWaitIdle(device.device)
    first_present := true
    for !glfw.WindowShouldClose(window_handle) {
    glfw.PollEvents()
    render_frame(device, surface, &swapchain, &rendering)
    if first_present {
    first_present = false
    glfw.ShowWindow(window_handle)
    }
    }
    glfw.Init()
    defer glfw.Terminate()

    glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
    glfw.WindowHint(glfw.RESIZABLE, glfw.TRUE)
    glfw.WindowHint(glfw.VISIBLE, glfw.FALSE);

    window_handle := glfw.CreateWindow(1000, 1000, "Vulkan Window", nil, nil)
    assert(window_handle != nil)
    defer glfw.DestroyWindow(window_handle)

    instance := init_vulkan_instance()
    defer vk.DestroyInstance(instance.instance, nil)
    surface := init_vulkan_surface(instance, window_handle)
    defer vk.DestroySurfaceKHR(instance.instance, surface.surface, nil)
    device := init_vulkan_device(instance, surface)
    defer vk.DestroyDevice(device.device, nil)
    swapchain := init_vulkan_swapchain(device, surface)
    defer destroy_swapchain(device, &swapchain)
    rendering := init_rendering(device)
    defer destroy_rendering(device, &rendering)
    defer vk.DeviceWaitIdle(device.device)

    first_present := true
    for !glfw.WindowShouldClose(window_handle) {
    glfw.PollEvents()
    render_frame(device, surface, &swapchain, &rendering)
    if first_present {
    first_present = false
    glfw.ShowWindow(window_handle)
    }
    }

    }

    @@ -409,7 +409,7 @@ init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {

    destroy_swapchain :: proc(device : Device, swapchain : ^Swapchain) {
    for image_view in swapchain.image_views {
    vk.DestroyImageView(device.device, image_view, nil);
    vk.DestroyImageView(device.device, image_view, nil);
    }
    vk.DestroySwapchainKHR(device.device, swapchain.swapchain, nil);
    }
    @@ -428,7 +428,7 @@ init_rendering :: proc(device : Device) -> Rendering {
    frag, _ := mem.alloc_bytes(len(fragment_shader_bytecode), 4)
    mem.copy(raw_data(frag), raw_data(fragment_shader_bytecode), len(fragment_shader_bytecode))
    defer delete(frag)

    shaderCreateInfos := [2]vk.ShaderCreateInfoEXT {
    {
    sType = vk.StructureType.SHADER_CREATE_INFO_EXT,
    @@ -493,126 +493,125 @@ init_rendering :: proc(device : Device) -> Rendering {
    }

    destroy_rendering :: proc(device : Device, rendering : ^Rendering) {
    vk.DestroyShaderEXT(device.device, rendering.shaders[0], nil)
    vk.DestroyShaderEXT(device.device, rendering.shaders[1], nil)
    vk.FreeCommandBuffers(device.device, rendering.command_pool, 1, &rendering.command_buffer)
    vk.DestroyCommandPool(device.device, rendering.command_pool, nil)
    vk.DestroySemaphore(device.device, rendering.image_available_semaphore, nil)
    vk.DestroySemaphore(device.device, rendering.render_finished_semaphore, nil)
    vk.DestroyFence(device.device, rendering.in_flight_fence, nil)
    vk.DestroyShaderEXT(device.device, rendering.shaders[0], nil)
    vk.DestroyShaderEXT(device.device, rendering.shaders[1], nil)
    vk.FreeCommandBuffers(device.device, rendering.command_pool, 1, &rendering.command_buffer)
    vk.DestroyCommandPool(device.device, rendering.command_pool, nil)
    vk.DestroySemaphore(device.device, rendering.image_available_semaphore, nil)
    vk.DestroySemaphore(device.device, rendering.render_finished_semaphore, nil)
    vk.DestroyFence(device.device, rendering.in_flight_fence, nil)
    }

    swapchain_barrier :: proc(command_buffer : vk.CommandBuffer, image : vk.Image, top : bool) {
    image_memory_barrier := vk.ImageMemoryBarrier{
    sType = vk.StructureType.IMAGE_MEMORY_BARRIER,
    dstAccessMask = top ? {vk.AccessFlag.COLOR_ATTACHMENT_WRITE} : {},
    srcAccessMask = top ? {} : {vk.AccessFlag.COLOR_ATTACHMENT_WRITE},
    oldLayout = top ? vk.ImageLayout.UNDEFINED : vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL,
    newLayout = top ? vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL : vk.ImageLayout.PRESENT_SRC_KHR,
    image = image,
    subresourceRange = {
    aspectMask = {vk.ImageAspectFlag.COLOR},
    baseMipLevel = 0,
    levelCount = 1,
    baseArrayLayer = 0,
    layerCount = 1,
    }
    image_memory_barrier := vk.ImageMemoryBarrier{
    sType = vk.StructureType.IMAGE_MEMORY_BARRIER,
    dstAccessMask = top ? {vk.AccessFlag.COLOR_ATTACHMENT_WRITE} : {},
    srcAccessMask = top ? {} : {vk.AccessFlag.COLOR_ATTACHMENT_WRITE},
    oldLayout = top ? vk.ImageLayout.UNDEFINED : vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL,
    newLayout = top ? vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL : vk.ImageLayout.PRESENT_SRC_KHR,
    image = image,
    subresourceRange = {
    aspectMask = {vk.ImageAspectFlag.COLOR},
    baseMipLevel = 0,
    levelCount = 1,
    baseArrayLayer = 0,
    layerCount = 1,
    }
    }

    vk.CmdPipelineBarrier(
    command_buffer,
    top ? {vk.PipelineStageFlag.TOP_OF_PIPE} : {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT}, // srcStageMask
    top ? {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT} : {vk.PipelineStageFlag.BOTTOM_OF_PIPE}, // dstStageMask
    {},
    {},
    nil,
    {},
    nil,
    1, // imageMemoryBarrierCount
    &image_memory_barrier // pImageMemoryBarriers
    )
    vk.CmdPipelineBarrier(
    command_buffer,
    top ? {vk.PipelineStageFlag.TOP_OF_PIPE} : {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT}, // srcStageMask
    top ? {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT} : {vk.PipelineStageFlag.BOTTOM_OF_PIPE}, // dstStageMask
    {},
    {},
    nil,
    {},
    nil,
    1, // imageMemoryBarrierCount
    &image_memory_barrier // pImageMemoryBarriers
    )
    }

    record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, image_index : u32) {
    cbbi : vk.CommandBufferBeginInfo
    cbbi.sType = .COMMAND_BUFFER_BEGIN_INFO
    cbbi : vk.CommandBufferBeginInfo
    cbbi.sType = .COMMAND_BUFFER_BEGIN_INFO

    vkok(vk.BeginCommandBuffer(rendering.command_buffer, &cbbi))
    vkok(vk.BeginCommandBuffer(rendering.command_buffer, &cbbi))

    clear_color := vk.ClearValue{
    color = { float32 = {0,0,0,1}}
    }
    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], true)

    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], true)

    color_attachment_info := vk.RenderingAttachmentInfoKHR{
    sType = vk.StructureType.RENDERING_ATTACHMENT_INFO_KHR,
    imageView = swapchain.image_views[image_index],
    imageLayout = vk.ImageLayout.ATTACHMENT_OPTIMAL,
    loadOp = vk.AttachmentLoadOp.CLEAR,
    storeOp = vk.AttachmentStoreOp.STORE,
    clearValue = clear_color,
    }
    clear_color := vk.ClearValue{
    color = { float32 = {0,0,0,1}}
    }
    color_attachment_info := vk.RenderingAttachmentInfoKHR{
    sType = vk.StructureType.RENDERING_ATTACHMENT_INFO_KHR,
    imageView = swapchain.image_views[image_index],
    imageLayout = vk.ImageLayout.ATTACHMENT_OPTIMAL,
    loadOp = vk.AttachmentLoadOp.CLEAR,
    storeOp = vk.AttachmentStoreOp.STORE,
    clearValue = clear_color,
    }

    rendering_info := vk.RenderingInfoKHR{
    sType = vk.StructureType.RENDERING_INFO_KHR,
    renderArea = {
    offset = {0, 0},
    extent = swapchain.extent,
    },
    layerCount = 1,
    colorAttachmentCount = 1,
    pColorAttachments = &color_attachment_info,
    }
    rendering_info := vk.RenderingInfoKHR{
    sType = vk.StructureType.RENDERING_INFO_KHR,
    renderArea = {
    offset = {0, 0},
    extent = swapchain.extent,
    },
    layerCount = 1,
    colorAttachmentCount = 1,
    pColorAttachments = &color_attachment_info,
    }

    vk.CmdBeginRenderingKHR(rendering.command_buffer, &rendering_info)

    // Set pipeline state
    viewport := vk.Viewport{0, 0, f32(swapchain.extent.width), f32(swapchain.extent.height), 0, 1}
    scissor := vk.Rect2D{{0,0}, swapchain.extent}
    vk.CmdSetViewportWithCountEXT(rendering.command_buffer, 1, &viewport)
    vk.CmdSetScissorWithCountEXT(rendering.command_buffer, 1, &scissor)
    vk.CmdSetCullModeEXT(rendering.command_buffer, {vk.CullModeFlag.BACK})
    vk.CmdSetFrontFaceEXT(rendering.command_buffer, vk.FrontFace.CLOCKWISE)
    vk.CmdSetDepthTestEnableEXT(rendering.command_buffer, true)
    vk.CmdSetDepthWriteEnableEXT(rendering.command_buffer, true)
    vk.CmdSetDepthCompareOpEXT(rendering.command_buffer, vk.CompareOp.LESS_OR_EQUAL)
    vk.CmdSetPrimitiveTopologyEXT(rendering.command_buffer, vk.PrimitiveTopology.TRIANGLE_LIST)
    vk.CmdSetRasterizerDiscardEnableEXT(rendering.command_buffer, false)
    vk.CmdSetPolygonModeEXT(rendering.command_buffer, vk.PolygonMode.FILL)
    vk.CmdSetRasterizationSamplesEXT(rendering.command_buffer, {vk.SampleCountFlag._1})
    vk.CmdSetAlphaToCoverageEnableEXT(rendering.command_buffer, false)
    vk.CmdSetDepthBiasEnableEXT(rendering.command_buffer, false)
    vk.CmdSetStencilTestEnableEXT(rendering.command_buffer, false)
    vk.CmdSetPrimitiveRestartEnableEXT(rendering.command_buffer, false)
    sampleMask := vk.SampleMask(0xFF)
    vk.CmdSetSampleMaskEXT(rendering.command_buffer, {vk.SampleCountFlag._1}, &sampleMask)
    colorBlendEnables := b32(false)
    vk.CmdSetColorBlendEnableEXT(rendering.command_buffer, 0, 1, &colorBlendEnables)
    colorBlendComponentFlags := vk.ColorComponentFlags{.R, .G, .B, .A}
    vk.CmdSetColorWriteMaskEXT(rendering.command_buffer, 0, 1, &colorBlendComponentFlags)
    colorBlendEquations := vk.ColorBlendEquationEXT{
    srcColorBlendFactor = vk.BlendFactor.SRC_ALPHA,
    dstColorBlendFactor = .ONE_MINUS_SRC_ALPHA,
    colorBlendOp = .ADD,
    srcAlphaBlendFactor = .ONE,
    dstAlphaBlendFactor = .ZERO,
    alphaBlendOp = .ADD,
    }
    vk.CmdSetColorBlendEquationEXT(rendering.command_buffer, 0, 1, &colorBlendEquations)
    vk.CmdBeginRenderingKHR(rendering.command_buffer, &rendering_info)

    // Set pipeline state
    viewport := vk.Viewport{0, 0, f32(swapchain.extent.width), f32(swapchain.extent.height), 0, 1}
    scissor := vk.Rect2D{{0,0}, swapchain.extent}
    vk.CmdSetViewportWithCountEXT(rendering.command_buffer, 1, &viewport)
    vk.CmdSetScissorWithCountEXT(rendering.command_buffer, 1, &scissor)
    vk.CmdSetCullModeEXT(rendering.command_buffer, {vk.CullModeFlag.BACK})
    vk.CmdSetFrontFaceEXT(rendering.command_buffer, vk.FrontFace.CLOCKWISE)
    vk.CmdSetDepthTestEnableEXT(rendering.command_buffer, true)
    vk.CmdSetDepthWriteEnableEXT(rendering.command_buffer, true)
    vk.CmdSetDepthCompareOpEXT(rendering.command_buffer, vk.CompareOp.LESS_OR_EQUAL)
    vk.CmdSetPrimitiveTopologyEXT(rendering.command_buffer, vk.PrimitiveTopology.TRIANGLE_LIST)
    vk.CmdSetRasterizerDiscardEnableEXT(rendering.command_buffer, false)
    vk.CmdSetPolygonModeEXT(rendering.command_buffer, vk.PolygonMode.FILL)
    vk.CmdSetRasterizationSamplesEXT(rendering.command_buffer, {vk.SampleCountFlag._1})
    vk.CmdSetAlphaToCoverageEnableEXT(rendering.command_buffer, false)
    vk.CmdSetDepthBiasEnableEXT(rendering.command_buffer, false)
    vk.CmdSetStencilTestEnableEXT(rendering.command_buffer, false)
    vk.CmdSetPrimitiveRestartEnableEXT(rendering.command_buffer, false)
    sampleMask := vk.SampleMask(0xFF)
    vk.CmdSetSampleMaskEXT(rendering.command_buffer, {vk.SampleCountFlag._1}, &sampleMask)
    colorBlendEnables := b32(false)
    vk.CmdSetColorBlendEnableEXT(rendering.command_buffer, 0, 1, &colorBlendEnables)
    colorBlendComponentFlags := vk.ColorComponentFlags{.R, .G, .B, .A}
    vk.CmdSetColorWriteMaskEXT(rendering.command_buffer, 0, 1, &colorBlendComponentFlags)
    colorBlendEquations := vk.ColorBlendEquationEXT{
    srcColorBlendFactor = vk.BlendFactor.SRC_ALPHA,
    dstColorBlendFactor = .ONE_MINUS_SRC_ALPHA,
    colorBlendOp = .ADD,
    srcAlphaBlendFactor = .ONE,
    dstAlphaBlendFactor = .ZERO,
    alphaBlendOp = .ADD,
    }
    vk.CmdSetColorBlendEquationEXT(rendering.command_buffer, 0, 1, &colorBlendEquations)

    // Bind shaders
    stages := [2]vk.ShaderStageFlags{{.VERTEX}, {.FRAGMENT}}
    vk.CmdBindShadersEXT(rendering.command_buffer, 2, &stages[0], &rendering.shaders[0])
    // Bind shaders
    stages := [2]vk.ShaderStageFlags{{.VERTEX}, {.FRAGMENT}}
    vk.CmdBindShadersEXT(rendering.command_buffer, 2, &stages[0], &rendering.shaders[0])

    // Submit draw
    vk.CmdDraw(rendering.command_buffer, 3, 1, 0, 0)
    // Submit draw
    vk.CmdDraw(rendering.command_buffer, 3, 1, 0, 0)

    vk.CmdEndRenderingKHR(rendering.command_buffer)
    vk.CmdEndRenderingKHR(rendering.command_buffer)

    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], false)
    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], false)

    vkok(vk.EndCommandBuffer(rendering.command_buffer))
    vkok(vk.EndCommandBuffer(rendering.command_buffer))
    }

    render_frame :: proc(device : Device, surface : Surface, swapchain : ^Swapchain, rendering : ^Rendering) {
  8. jockus created this gist Jun 26, 2024.
    663 changes: 663 additions & 0 deletions minimal_modern_vulkan.odin
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,663 @@
    package vulkan_tutorial

    import "base:runtime"
    import "core:fmt"
    import "core:c"
    import "core:dynlib"
    import "core:strings"
    import "core:mem"
    import "vendor:glfw"
    import vk "vendor:vulkan"

    DB :: "DEBUG:"
    DBVB :: "DEBUG (VERBOSE):"
    ERROR :: "ERROR:"
    WARNING :: "WARNING:"

    vertex_shader_bytecode :: #load("./shaders/vertex.spv")
    fragment_shader_bytecode :: #load("./shaders/fragment.spv")

    Instance :: struct {
    instance : vk.Instance
    }

    Surface :: struct {
    surface : vk.SurfaceKHR
    }

    Device :: struct {
    physical_device : vk.PhysicalDevice,
    device : vk.Device,
    graphics_queue : vk.Queue,
    graphics_family_queue_index : u32,
    }

    Swapchain :: struct {
    swapchain : vk.SwapchainKHR,
    images : []vk.Image,
    image_views : []vk.ImageView,
    extent : vk.Extent2D,
    format : vk.Format,
    }

    Rendering :: struct {
    shaders : [2]vk.ShaderEXT,
    command_pool : vk.CommandPool,
    command_buffer : vk.CommandBuffer,
    image_available_semaphore : vk.Semaphore,
    render_finished_semaphore : vk.Semaphore,
    in_flight_fence : vk.Fence,
    }

    vkok :: proc(result : vk.Result, loc := #caller_location) {
    #partial switch result {
    case .SUCCESS:
    case .ERROR_OUT_OF_HOST_MEMORY:
    fmt.eprintln(ERROR, "Out of host memory.")
    case .ERROR_OUT_OF_DEVICE_MEMORY:
    fmt.eprintln(ERROR, "Out of device memory.")
    case .ERROR_INITIALIZATION_FAILED:
    fmt.eprintln(ERROR, "Initialization failed.")
    case .ERROR_LAYER_NOT_PRESENT:
    fmt.eprintln(ERROR, "Layer not present.")
    case .ERROR_EXTENSION_NOT_PRESENT:
    fmt.eprintln(ERROR, "Extension not present.")
    case .ERROR_INCOMPATIBLE_DRIVER:
    fmt.eprintln(ERROR, "Incompatible driver.")
    case:
    fmt.eprintln(ERROR, "Other Spec 1.3 Error!")
    }
    if result != .SUCCESS {
    fmt.println(ERROR, "vk function result was not .SUCCESS")
    fmt.println( "Result was instead:", result)
    fmt.println(" Error at:")
    fmt.println(" ", loc)
    assert(false)
    }
    }

    main :: proc() {
    glfw.Init()
    defer glfw.Terminate()

    glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
    glfw.WindowHint(glfw.RESIZABLE, glfw.TRUE)
    glfw.WindowHint(glfw.VISIBLE, glfw.FALSE);

    window_handle := glfw.CreateWindow(1000, 1000, "Vulkan Window", nil, nil)
    assert(window_handle != nil)
    defer glfw.DestroyWindow(window_handle)

    instance := init_vulkan_instance()
    defer vk.DestroyInstance(instance.instance, nil)
    surface := init_vulkan_surface(instance, window_handle)
    defer vk.DestroySurfaceKHR(instance.instance, surface.surface, nil)
    device := init_vulkan_device(instance, surface)
    defer vk.DestroyDevice(device.device, nil)
    swapchain := init_vulkan_swapchain(device, surface)
    defer destroy_swapchain(device, &swapchain)
    rendering := init_rendering(device)
    defer destroy_rendering(device, &rendering)
    defer vk.DeviceWaitIdle(device.device)

    first_present := true
    for !glfw.WindowShouldClose(window_handle) {
    glfw.PollEvents()
    render_frame(device, surface, &swapchain, &rendering)
    if first_present {
    first_present = false
    glfw.ShowWindow(window_handle)
    }
    }

    }

    init_vulkan_instance :: proc() -> Instance {
    vulkan_lib, loaded := dynlib.load_library("vulkan-1.dll")
    assert(loaded)

    vkGetInstanceProcAddr, found := dynlib.symbol_address(vulkan_lib, "vkGetInstanceProcAddr")
    assert(found)

    vk.load_proc_addresses_global(vkGetInstanceProcAddr)

    appinfo := vk.ApplicationInfo {
    sType = vk.StructureType.APPLICATION_INFO,
    pApplicationName = "Minimal Modern Vulkan",
    applicationVersion = vk.MAKE_VERSION(1,0,0),
    pEngineName = "Minimal Modern Vulkan",
    engineVersion = vk.MAKE_VERSION(1,0,0),
    apiVersion = vk.API_VERSION_1_3,
    }

    required_layers : [dynamic] cstring
    defer delete(required_layers)
    when ODIN_DEBUG {
    append(&required_layers, "VK_LAYER_KHRONOS_validation")
    // append(&required_layers, "VK_LAYER_KHRONOS_shader_object")
    }

    // Ensure layers
    supported_layer_count : u32
    vkok(vk.EnumerateInstanceLayerProperties(&supported_layer_count, nil))
    supported_layers := make([] vk.LayerProperties, supported_layer_count)
    defer delete(supported_layers)
    vkok(vk.EnumerateInstanceLayerProperties(&supported_layer_count, raw_data(supported_layers)))
    for required_layer in required_layers {
    found : bool
    for &supported_layer in supported_layers {
    if required_layer == cstring(&supported_layer.layerName[0]) {
    found = true
    break
    }
    }
    if !found {
    fmt.println(ERROR, "A required layer:", required_layer, "is missing!")
    assert(false)
    }
    }

    // Ensure extensions
    required_extensions : [dynamic]cstring
    for ext in glfw.GetRequiredInstanceExtensions() {
    append(&required_extensions, ext)
    }
    supported_extension_count : u32
    vkok(vk.EnumerateInstanceExtensionProperties(nil, &supported_extension_count, nil))
    supported_extensions := make([]vk.ExtensionProperties, supported_extension_count)
    defer delete(supported_extensions)
    vkok(vk.EnumerateInstanceExtensionProperties(nil, &supported_extension_count, raw_data(supported_extensions)))
    for required_extension in required_extensions {
    found : bool
    for &supported_extension in supported_extensions {
    if required_extension == cstring(&supported_extension.extensionName[0]) {
    found = true
    break
    }
    }
    if !found {
    fmt.println(ERROR, "A required extension:", required_extension, "is missing!")
    assert(false)
    }
    }

    icreateinfo := vk.InstanceCreateInfo {
    sType = vk.StructureType.INSTANCE_CREATE_INFO,
    pApplicationInfo = &appinfo,
    enabledLayerCount = u32(len(required_layers)),
    ppEnabledLayerNames = raw_data(required_layers),
    enabledExtensionCount = u32(len(required_extensions)),
    ppEnabledExtensionNames = raw_data(required_extensions),
    }

    instance : vk.Instance
    vkok(vk.CreateInstance(&icreateinfo, nil, &instance))

    vk.load_proc_addresses(instance)

    return Instance{instance}
    }

    init_vulkan_surface :: proc(instance : Instance, window_handle : glfw.WindowHandle) -> Surface {
    surface : vk.SurfaceKHR
    vkok(glfw.CreateWindowSurface(instance.instance, window_handle, nil, &surface))
    return Surface{surface}
    }

    init_vulkan_device :: proc(instance : Instance, surface : Surface) -> Device {
    pds_count : u32
    vkok(vk.EnumeratePhysicalDevices(instance.instance, &pds_count, nil))
    physical_devices := make([]vk.PhysicalDevice, pds_count)
    defer delete(physical_devices)
    vkok(vk.EnumeratePhysicalDevices(instance.instance, &pds_count, raw_data(physical_devices)))

    pdevice_required_extensions := []cstring{
    vk.KHR_SWAPCHAIN_EXTENSION_NAME,
    vk.KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
    vk.EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME,
    vk.EXT_SHADER_OBJECT_EXTENSION_NAME,
    }

    at_least_one_pdevice_okay : bool
    okay_pdevice : vk.PhysicalDevice
    pdevice_properties : vk.PhysicalDeviceProperties
    graphics_family_queue_index : u32
    for pdevice in physical_devices {
    pd_properties : vk.PhysicalDeviceProperties
    vk.GetPhysicalDeviceProperties(pdevice, &pd_properties)

    pdevice_supported_extensions_count : u32
    vkok(vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, nil))
    pdevice_supported_extensions := make([]vk.ExtensionProperties, pdevice_supported_extensions_count)
    vkok(vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, raw_data(pdevice_supported_extensions)))

    has_required_extensions := true
    for required_extension in pdevice_required_extensions {
    found : bool
    for &supported_extension in pdevice_supported_extensions {
    if required_extension == cstring(&supported_extension.extensionName[0]) {
    found = true
    break
    }
    }
    if !found {
    has_required_extensions = false
    break
    }
    }

    has_required_queue_family : bool
    queue_count : u32
    vk.GetPhysicalDeviceQueueFamilyProperties(pdevice, &queue_count, nil)
    qf_properties := make([]vk.QueueFamilyProperties, queue_count)
    defer delete(qf_properties)
    vk.GetPhysicalDeviceQueueFamilyProperties(pdevice, &queue_count, raw_data(qf_properties))
    for queue_family, queue_family_index in qf_properties {
    supports_present : b32
    vkok(vk.GetPhysicalDeviceSurfaceSupportKHR(pdevice, u32(queue_family_index), surface.surface, &supports_present))
    if vk.QueueFlag.GRAPHICS in queue_family.queueFlags && supports_present {
    graphics_family_queue_index = u32(queue_family_index)
    has_required_queue_family = true
    break
    }
    }

    // TODO: Ensure wanted surface format is available?

    if has_required_extensions && has_required_queue_family {
    at_least_one_pdevice_okay = true
    okay_pdevice = pdevice
    pdevice_properties = pd_properties
    }
    }

    if !at_least_one_pdevice_okay {
    fmt.eprintln("No physical devices have all the required extensions")
    assert(false)
    }
    pdevice : vk.PhysicalDevice
    pdevice = okay_pdevice

    queue_priority_graphics : f32 = 1
    qci_graphics := vk.DeviceQueueCreateInfo {
    sType = vk.StructureType.DEVICE_QUEUE_CREATE_INFO,
    queueFamilyIndex = graphics_family_queue_index,
    queueCount = 1.0,
    pQueuePriorities = &queue_priority_graphics,
    }

    enabledShaderObjectFeaturesEXT := vk.PhysicalDeviceShaderObjectFeaturesEXT{
    sType = vk.StructureType.PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT,
    shaderObject = true,
    }
    dynamic_rendering_feature := vk.PhysicalDeviceDynamicRenderingFeaturesKHR{
    sType = vk.StructureType.PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
    dynamicRendering = true,
    pNext = &enabledShaderObjectFeaturesEXT
    }
    device_features : vk.PhysicalDeviceFeatures

    dci := vk.DeviceCreateInfo {
    sType = vk.StructureType.DEVICE_CREATE_INFO,
    queueCreateInfoCount = 1,
    pQueueCreateInfos = &qci_graphics,
    pEnabledFeatures = &device_features,
    enabledExtensionCount = u32(len(pdevice_required_extensions)),
    ppEnabledExtensionNames = raw_data(pdevice_required_extensions),
    pNext = &dynamic_rendering_feature,
    }

    device : vk.Device
    vkok(vk.CreateDevice(pdevice, &dci, nil, &device))

    graphics_queue : vk.Queue
    vk.GetDeviceQueue(device, graphics_family_queue_index, 0, &graphics_queue)

    return Device{pdevice, device, graphics_queue, graphics_family_queue_index}
    }

    init_vulkan_swapchain :: proc(device : Device, surface : Surface) -> Swapchain {
    supported_surface_formats_count : u32
    vkok(vk.GetPhysicalDeviceSurfaceFormatsKHR(device.physical_device, surface.surface, &supported_surface_formats_count, nil))
    supported_surface_formats := make([]vk.SurfaceFormatKHR, supported_surface_formats_count)
    defer delete(supported_surface_formats)
    vkok(vk.GetPhysicalDeviceSurfaceFormatsKHR(device.physical_device, surface.surface, &supported_surface_formats_count, raw_data(supported_surface_formats)))
    surface_format := supported_surface_formats[0]
    desired_surface_format := vk.SurfaceFormatKHR{
    format = .B8G8R8A8_SRGB,
    colorSpace = .SRGB_NONLINEAR,
    }
    for sf in supported_surface_formats {
    if sf == desired_surface_format {
    surface_format = desired_surface_format
    break
    }
    }

    supported_present_modes_count : u32
    vkok(vk.GetPhysicalDeviceSurfacePresentModesKHR(device.physical_device, surface.surface, &supported_present_modes_count, nil))
    supported_present_modes := make([]vk.PresentModeKHR, supported_present_modes_count)
    defer delete(supported_present_modes)
    vkok(vk.GetPhysicalDeviceSurfacePresentModesKHR(device.physical_device, surface.surface, &supported_present_modes_count, raw_data(supported_present_modes)))
    present_mode := vk.PresentModeKHR.FIFO
    desired_present_mode := vk.PresentModeKHR.MAILBOX
    for pm in supported_present_modes {
    if pm == desired_present_mode {
    present_mode = desired_present_mode
    }
    }

    // TODO: Just handle double/triple buffer
    surface_capabilities : vk.SurfaceCapabilitiesKHR
    vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(device.physical_device, surface.surface, &surface_capabilities)
    surface_image_count := min(surface_capabilities.minImageCount + 1, surface_capabilities.maxImageCount)
    extent_special_value := vk.Extent2D{max(u32), max(u32)}
    extent := surface_capabilities.currentExtent
    if extent == extent_special_value {
    extent = surface_capabilities.minImageExtent
    }

    swpcnci := vk.SwapchainCreateInfoKHR {
    sType = .SWAPCHAIN_CREATE_INFO_KHR,
    surface = surface.surface,
    minImageCount = surface_image_count,
    imageFormat = surface_format.format,
    imageColorSpace = surface_format.colorSpace,
    imageExtent = extent,
    imageArrayLayers = 1,
    imageUsage = { vk.ImageUsageFlag.COLOR_ATTACHMENT },
    imageSharingMode = .EXCLUSIVE,
    preTransform = surface_capabilities.currentTransform,
    compositeAlpha = { vk.CompositeAlphaFlagKHR.OPAQUE },
    presentMode = present_mode,
    clipped = true,
    }

    swapchain : vk.SwapchainKHR
    vkok(vk.CreateSwapchainKHR(device.device, &swpcnci, nil, &swapchain))

    device_image_count : u32
    vkok(vk.GetSwapchainImagesKHR(device.device, swapchain, &device_image_count, nil))
    swapchain_images := make([]vk.Image, device_image_count)
    vkok(vk.GetSwapchainImagesKHR(device.device, swapchain, &device_image_count, raw_data(swapchain_images)))

    swapchain_imageviews := make([]vk.ImageView, device_image_count)
    for i in 0..<device_image_count {
    imvci := vk.ImageViewCreateInfo {
    sType = .IMAGE_VIEW_CREATE_INFO,
    image = swapchain_images[i],
    viewType = vk.ImageViewType.D2,
    format = surface_format.format,
    components = {
    r = .IDENTITY,
    g = .IDENTITY,
    b = .IDENTITY,
    a = .IDENTITY,
    },
    subresourceRange = {
    aspectMask = { .COLOR },
    baseMipLevel = 0,
    levelCount = 1,
    baseArrayLayer = 0,
    layerCount = 1,
    },
    }
    vkok(vk.CreateImageView(device.device, &imvci, nil, &swapchain_imageviews[i]))
    }
    return Swapchain{swapchain, swapchain_images, swapchain_imageviews, extent, surface_format.format}
    }

    destroy_swapchain :: proc(device : Device, swapchain : ^Swapchain) {
    for image_view in swapchain.image_views {
    vk.DestroyImageView(device.device, image_view, nil);
    }
    vk.DestroySwapchainKHR(device.device, swapchain.swapchain, nil);
    }

    recreate_swapchain :: proc(device : Device, surface : Surface, swapchain : ^Swapchain) {
    vk.DeviceWaitIdle(device.device)
    destroy_swapchain(device, swapchain)
    swapchain^ = init_vulkan_swapchain(device, surface)
    }

    init_rendering :: proc(device : Device) -> Rendering {
    // Ensure source is 4 bytes aligned
    vertex, _ := mem.alloc_bytes(len(vertex_shader_bytecode), 4)
    mem.copy(raw_data(vertex), raw_data(vertex_shader_bytecode), len(vertex_shader_bytecode))
    defer delete(vertex)
    frag, _ := mem.alloc_bytes(len(fragment_shader_bytecode), 4)
    mem.copy(raw_data(frag), raw_data(fragment_shader_bytecode), len(fragment_shader_bytecode))
    defer delete(frag)

    shaderCreateInfos := [2]vk.ShaderCreateInfoEXT {
    {
    sType = vk.StructureType.SHADER_CREATE_INFO_EXT,
    flags = {vk.ShaderCreateFlagEXT.LINK_STAGE},
    stage = {vk.ShaderStageFlag.VERTEX},
    nextStage = {vk.ShaderStageFlag.FRAGMENT},
    codeType = vk.ShaderCodeTypeEXT.SPIRV,
    pCode = raw_data(vertex),
    codeSize = len(vertex),
    pName = "main",
    setLayoutCount = 0,
    // pSetLayouts = &descriptorSetLayout,
    },
    {
    sType = vk.StructureType.SHADER_CREATE_INFO_EXT,
    flags = {vk.ShaderCreateFlagEXT.LINK_STAGE},
    stage = {vk.ShaderStageFlag.FRAGMENT},
    nextStage = {},
    codeType = vk.ShaderCodeTypeEXT.SPIRV,
    pCode = raw_data(frag),
    codeSize = len(frag),
    pName = "main",
    setLayoutCount = 0,
    // pSetLayouts = &descriptorSetLayout,
    }
    }
    shaders : [2]vk.ShaderEXT
    vkok(vk.CreateShadersEXT(device.device, 2, &shaderCreateInfos[0], nil, &shaders[0]))

    cpci := vk.CommandPoolCreateInfo {
    sType = .COMMAND_POOL_CREATE_INFO,
    flags = { vk.CommandPoolCreateFlags.RESET_COMMAND_BUFFER },
    queueFamilyIndex = device.graphics_family_queue_index,
    }
    command_pool : vk.CommandPool
    vkok(vk.CreateCommandPool(device.device, &cpci, nil, &command_pool))

    cbai := vk.CommandBufferAllocateInfo {
    sType = .COMMAND_BUFFER_ALLOCATE_INFO,
    commandPool = command_pool,
    level = .PRIMARY,
    commandBufferCount = 1,
    }
    command_buffer : vk.CommandBuffer
    vkok(vk.AllocateCommandBuffers(device.device, &cbai, &command_buffer))

    sci := vk.SemaphoreCreateInfo {
    sType = .SEMAPHORE_CREATE_INFO
    }
    image_available_semaphore : vk.Semaphore
    vkok(vk.CreateSemaphore(device.device, &sci, nil, &image_available_semaphore))
    render_finished_semaphore : vk.Semaphore
    vkok(vk.CreateSemaphore(device.device, &sci, nil, &render_finished_semaphore))

    fci := vk.FenceCreateInfo {
    sType = .FENCE_CREATE_INFO,
    flags = { .SIGNALED },
    }
    in_flight_fence : vk.Fence
    vkok(vk.CreateFence(device.device, &fci, nil, &in_flight_fence))
    return Rendering{shaders, command_pool, command_buffer, image_available_semaphore, render_finished_semaphore, in_flight_fence}
    }

    destroy_rendering :: proc(device : Device, rendering : ^Rendering) {
    vk.DestroyShaderEXT(device.device, rendering.shaders[0], nil)
    vk.DestroyShaderEXT(device.device, rendering.shaders[1], nil)
    vk.FreeCommandBuffers(device.device, rendering.command_pool, 1, &rendering.command_buffer)
    vk.DestroyCommandPool(device.device, rendering.command_pool, nil)
    vk.DestroySemaphore(device.device, rendering.image_available_semaphore, nil)
    vk.DestroySemaphore(device.device, rendering.render_finished_semaphore, nil)
    vk.DestroyFence(device.device, rendering.in_flight_fence, nil)
    }

    swapchain_barrier :: proc(command_buffer : vk.CommandBuffer, image : vk.Image, top : bool) {
    image_memory_barrier := vk.ImageMemoryBarrier{
    sType = vk.StructureType.IMAGE_MEMORY_BARRIER,
    dstAccessMask = top ? {vk.AccessFlag.COLOR_ATTACHMENT_WRITE} : {},
    srcAccessMask = top ? {} : {vk.AccessFlag.COLOR_ATTACHMENT_WRITE},
    oldLayout = top ? vk.ImageLayout.UNDEFINED : vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL,
    newLayout = top ? vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL : vk.ImageLayout.PRESENT_SRC_KHR,
    image = image,
    subresourceRange = {
    aspectMask = {vk.ImageAspectFlag.COLOR},
    baseMipLevel = 0,
    levelCount = 1,
    baseArrayLayer = 0,
    layerCount = 1,
    }
    }

    vk.CmdPipelineBarrier(
    command_buffer,
    top ? {vk.PipelineStageFlag.TOP_OF_PIPE} : {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT}, // srcStageMask
    top ? {vk.PipelineStageFlag.COLOR_ATTACHMENT_OUTPUT} : {vk.PipelineStageFlag.BOTTOM_OF_PIPE}, // dstStageMask
    {},
    {},
    nil,
    {},
    nil,
    1, // imageMemoryBarrierCount
    &image_memory_barrier // pImageMemoryBarriers
    )
    }

    record_command_buffer :: proc(swapchain : Swapchain, rendering : ^Rendering, image_index : u32) {
    cbbi : vk.CommandBufferBeginInfo
    cbbi.sType = .COMMAND_BUFFER_BEGIN_INFO

    vkok(vk.BeginCommandBuffer(rendering.command_buffer, &cbbi))

    clear_color := vk.ClearValue{
    color = { float32 = {0,0,0,1}}
    }

    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], true)

    color_attachment_info := vk.RenderingAttachmentInfoKHR{
    sType = vk.StructureType.RENDERING_ATTACHMENT_INFO_KHR,
    imageView = swapchain.image_views[image_index],
    imageLayout = vk.ImageLayout.ATTACHMENT_OPTIMAL,
    loadOp = vk.AttachmentLoadOp.CLEAR,
    storeOp = vk.AttachmentStoreOp.STORE,
    clearValue = clear_color,
    }

    rendering_info := vk.RenderingInfoKHR{
    sType = vk.StructureType.RENDERING_INFO_KHR,
    renderArea = {
    offset = {0, 0},
    extent = swapchain.extent,
    },
    layerCount = 1,
    colorAttachmentCount = 1,
    pColorAttachments = &color_attachment_info,
    }

    vk.CmdBeginRenderingKHR(rendering.command_buffer, &rendering_info)

    // Set pipeline state
    viewport := vk.Viewport{0, 0, f32(swapchain.extent.width), f32(swapchain.extent.height), 0, 1}
    scissor := vk.Rect2D{{0,0}, swapchain.extent}
    vk.CmdSetViewportWithCountEXT(rendering.command_buffer, 1, &viewport)
    vk.CmdSetScissorWithCountEXT(rendering.command_buffer, 1, &scissor)
    vk.CmdSetCullModeEXT(rendering.command_buffer, {vk.CullModeFlag.BACK})
    vk.CmdSetFrontFaceEXT(rendering.command_buffer, vk.FrontFace.CLOCKWISE)
    vk.CmdSetDepthTestEnableEXT(rendering.command_buffer, true)
    vk.CmdSetDepthWriteEnableEXT(rendering.command_buffer, true)
    vk.CmdSetDepthCompareOpEXT(rendering.command_buffer, vk.CompareOp.LESS_OR_EQUAL)
    vk.CmdSetPrimitiveTopologyEXT(rendering.command_buffer, vk.PrimitiveTopology.TRIANGLE_LIST)
    vk.CmdSetRasterizerDiscardEnableEXT(rendering.command_buffer, false)
    vk.CmdSetPolygonModeEXT(rendering.command_buffer, vk.PolygonMode.FILL)
    vk.CmdSetRasterizationSamplesEXT(rendering.command_buffer, {vk.SampleCountFlag._1})
    vk.CmdSetAlphaToCoverageEnableEXT(rendering.command_buffer, false)
    vk.CmdSetDepthBiasEnableEXT(rendering.command_buffer, false)
    vk.CmdSetStencilTestEnableEXT(rendering.command_buffer, false)
    vk.CmdSetPrimitiveRestartEnableEXT(rendering.command_buffer, false)
    sampleMask := vk.SampleMask(0xFF)
    vk.CmdSetSampleMaskEXT(rendering.command_buffer, {vk.SampleCountFlag._1}, &sampleMask)
    colorBlendEnables := b32(false)
    vk.CmdSetColorBlendEnableEXT(rendering.command_buffer, 0, 1, &colorBlendEnables)
    colorBlendComponentFlags := vk.ColorComponentFlags{.R, .G, .B, .A}
    vk.CmdSetColorWriteMaskEXT(rendering.command_buffer, 0, 1, &colorBlendComponentFlags)
    colorBlendEquations := vk.ColorBlendEquationEXT{
    srcColorBlendFactor = vk.BlendFactor.SRC_ALPHA,
    dstColorBlendFactor = .ONE_MINUS_SRC_ALPHA,
    colorBlendOp = .ADD,
    srcAlphaBlendFactor = .ONE,
    dstAlphaBlendFactor = .ZERO,
    alphaBlendOp = .ADD,
    }
    vk.CmdSetColorBlendEquationEXT(rendering.command_buffer, 0, 1, &colorBlendEquations)

    // Bind shaders
    stages := [2]vk.ShaderStageFlags{{.VERTEX}, {.FRAGMENT}}
    vk.CmdBindShadersEXT(rendering.command_buffer, 2, &stages[0], &rendering.shaders[0])

    // Submit draw
    vk.CmdDraw(rendering.command_buffer, 3, 1, 0, 0)

    vk.CmdEndRenderingKHR(rendering.command_buffer)

    swapchain_barrier(rendering.command_buffer, swapchain.images[image_index], false)

    vkok(vk.EndCommandBuffer(rendering.command_buffer))
    }

    render_frame :: proc(device : Device, surface : Surface, swapchain : ^Swapchain, rendering : ^Rendering) {
    vkok(vk.WaitForFences(device.device, 1, &rendering.in_flight_fence, true, max(u64)))

    image_index : u32
    acquire_result := vk.AcquireNextImageKHR(device.device, swapchain.swapchain, max(u64), rendering.image_available_semaphore, 0, &image_index)
    if acquire_result == vk.Result.ERROR_OUT_OF_DATE_KHR || acquire_result == vk.Result.SUBOPTIMAL_KHR {
    recreate_swapchain(device, surface, swapchain)
    return
    }
    else {
    vkok(acquire_result)
    }

    vkok(vk.ResetFences(device.device, 1, &rendering.in_flight_fence))
    vkok(vk.ResetCommandBuffer(rendering.command_buffer, {}))

    record_command_buffer(swapchain^, rendering, image_index)

    submit_info := vk.SubmitInfo{
    sType = .SUBMIT_INFO,
    waitSemaphoreCount = 1,
    pWaitSemaphores = &rendering.image_available_semaphore,
    pWaitDstStageMask = &vk.PipelineStageFlags{ .COLOR_ATTACHMENT_OUTPUT },
    commandBufferCount = 1,
    pCommandBuffers = &rendering.command_buffer,
    signalSemaphoreCount = 1,
    pSignalSemaphores = &rendering.render_finished_semaphore,
    }
    vkok(vk.QueueSubmit(device.graphics_queue, 1, &submit_info, rendering.in_flight_fence))

    present_info := vk.PresentInfoKHR{
    sType = .PRESENT_INFO_KHR,
    waitSemaphoreCount = 1,
    pWaitSemaphores = &rendering.render_finished_semaphore,
    swapchainCount = 1,
    pSwapchains = &swapchain.swapchain,
    pImageIndices = &image_index,
    }
    present_result := vk.QueuePresentKHR(device.graphics_queue, &present_info)
    if present_result == vk.Result.ERROR_OUT_OF_DATE_KHR || present_result == vk.Result.SUBOPTIMAL_KHR {
    recreate_swapchain(device, surface, swapchain)
    }
    else {
    vkok(present_result)
    }
    }