Last active
October 17, 2024 08:41
-
-
Save terickson001/bdaa52ce621a6c7f4120abba8959ffe6 to your computer and use it in GitHub Desktop.
vulkan-tutorial example in Odin
This file contains 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
import "shared:shaderc" | |
import "vendor:glfw" | |
import vk "vendor:vulkan" | |
MAX_FRAMES_IN_FLIGHT :: 2 | |
Context :: struct | |
{ | |
instance: vk.Instance, | |
device: vk.Device, | |
physical_device: vk.PhysicalDevice, | |
swap_chain: Swapchain, | |
pipeline: Pipeline, | |
queue_indices: [QueueFamily]int, | |
queues: [QueueFamily]vk.Queue, | |
surface: vk.SurfaceKHR, | |
window: glfw.WindowHandle, | |
command_pool: vk.CommandPool, | |
command_buffers: [MAX_FRAMES_IN_FLIGHT]vk.CommandBuffer, | |
vertex_buffer: Buffer, | |
index_buffer: Buffer, | |
image_available: [MAX_FRAMES_IN_FLIGHT]vk.Semaphore, | |
render_finished: [MAX_FRAMES_IN_FLIGHT]vk.Semaphore, | |
in_flight: [MAX_FRAMES_IN_FLIGHT]vk.Fence, | |
curr_frame: u32, | |
framebuffer_resized: bool, | |
} | |
Buffer :: struct | |
{ | |
buffer: vk.Buffer, | |
memory: vk.DeviceMemory, | |
length: int, | |
size: vk.DeviceSize, | |
} | |
Pipeline :: struct | |
{ | |
handle: vk.Pipeline, | |
render_pass: vk.RenderPass, | |
layout: vk.PipelineLayout, | |
} | |
QueueFamily :: enum | |
{ | |
Graphics, | |
Present, | |
} | |
Swapchain :: struct | |
{ | |
handle: vk.SwapchainKHR, | |
images: []vk.Image, | |
image_views: []vk.ImageView, | |
format: vk.SurfaceFormatKHR, | |
extent: vk.Extent2D, | |
present_mode: vk.PresentModeKHR, | |
image_count: u32, | |
support: SwapChainDetails, | |
framebuffers: []vk.Framebuffer, | |
} | |
SwapChainDetails :: struct | |
{ | |
capabilities: vk.SurfaceCapabilitiesKHR, | |
formats: []vk.SurfaceFormatKHR, | |
present_modes: []vk.PresentModeKHR, | |
} | |
Vertex :: struct | |
{ | |
pos: [2]f32, | |
color: [3]f32, | |
} | |
DEVICE_EXTENSIONS := [?]cstring{ | |
"VK_KHR_swapchain", | |
}; | |
VALIDATION_LAYERS := [?]cstring{"VK_LAYER_KHRONOS_validation"}; | |
main :: proc() | |
{ | |
using ctx: Context; | |
init_window(&ctx); | |
for q in &queue_indices do q = -1; | |
vertices := [?]Vertex{ | |
{{-0.5, -0.5}, {0.0, 0.0, 1.0}}, | |
{{ 0.5, -0.5}, {1.0, 0.0, 0.0}}, | |
{{ 0.5, 0.5}, {0.0, 1.0, 0.0}}, | |
{{-0.5, 0.5}, {1.0, 0.0, 0.0}}, | |
}; | |
indices := [?]u16{ | |
0, 1, 2, | |
2, 3, 0, | |
}; | |
init_vulkan(&ctx, vertices[:], indices[:]); | |
for !glfw.WindowShouldClose(window) | |
{ | |
glfw.PollEvents(); | |
draw_frame(&ctx, vertices[:], indices[:]); | |
} | |
vk.DeviceWaitIdle(device); | |
deinit_vulkan(&ctx); | |
glfw.DestroyWindow(window); | |
glfw.Terminate(); | |
} | |
VERTEX_BINDING := vk.VertexInputBindingDescription{ | |
binding = 0, | |
stride = size_of(Vertex), | |
inputRate = .VERTEX, | |
}; | |
VERTEX_ATTRIBUTES := [?]vk.VertexInputAttributeDescription{ | |
{ | |
binding = 0, | |
location = 0, | |
format = .R32G32_SFLOAT, | |
offset = cast(u32)offset_of(Vertex, pos), | |
}, | |
{ | |
binding = 0, | |
location = 1, | |
format = .R32G32B32_SFLOAT, | |
offset = cast(u32)offset_of(Vertex, color), | |
}, | |
}; | |
draw_frame :: proc(using ctx: ^Context, vertices: []Vertex, indices: []u16) | |
{ | |
vk.WaitForFences(device, 1, &in_flight[curr_frame], true, max(u64)); | |
image_index: u32; | |
res := vk.AcquireNextImageKHR(device, swap_chain.handle, max(u64), image_available[curr_frame], {}, &image_index); | |
if res == .ERROR_OUT_OF_DATE_KHR || res == .SUBOPTIMAL_KHR || framebuffer_resized | |
{ | |
framebuffer_resized = false; | |
recreate_swap_chain(ctx); | |
return; | |
} | |
else if res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed tp acquire swap chain image!\n"); | |
os.exit(1); | |
} | |
vk.ResetFences(device, 1, &in_flight[curr_frame]); | |
vk.ResetCommandBuffer(command_buffers[curr_frame], {}); | |
record_command_buffer(ctx, command_buffers[curr_frame], image_index); | |
submit_info: vk.SubmitInfo; | |
submit_info.sType = .SUBMIT_INFO; | |
wait_semaphores := [?]vk.Semaphore{image_available[curr_frame]}; | |
wait_stages := [?]vk.PipelineStageFlags{{.COLOR_ATTACHMENT_OUTPUT}}; | |
submit_info.waitSemaphoreCount = 1; | |
submit_info.pWaitSemaphores = &wait_semaphores[0]; | |
submit_info.pWaitDstStageMask = &wait_stages[0]; | |
submit_info.commandBufferCount = 1; | |
submit_info.pCommandBuffers = &command_buffers[curr_frame]; | |
signal_semaphores := [?]vk.Semaphore{render_finished[curr_frame]}; | |
submit_info.signalSemaphoreCount = 1; | |
submit_info.pSignalSemaphores = &signal_semaphores[0]; | |
if res := vk.QueueSubmit(queues[.Graphics], 1, &submit_info, in_flight[curr_frame]); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to submit draw command buffer!\n"); | |
os.exit(1); | |
} | |
present_info: vk.PresentInfoKHR; | |
present_info.sType = .PRESENT_INFO_KHR; | |
present_info.waitSemaphoreCount = 1; | |
present_info.pWaitSemaphores = &signal_semaphores[0]; | |
swap_chains := [?]vk.SwapchainKHR{swap_chain.handle}; | |
present_info.swapchainCount = 1; | |
present_info.pSwapchains = &swap_chains[0]; | |
present_info.pImageIndices = &image_index; | |
present_info.pResults = nil; | |
vk.QueuePresentKHR(queues[.Present], &present_info); | |
curr_frame = (curr_frame + 1) % MAX_FRAMES_IN_FLIGHT; | |
} | |
init_window :: proc(using ctx: ^Context) | |
{ | |
glfw.Init(); | |
glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API); | |
glfw.WindowHint(glfw.RESIZABLE, 0); | |
window = glfw.CreateWindow(800, 600, "Vulkan", nil, nil); | |
glfw.SetWindowUserPointer(window, ctx); | |
glfw.SetFramebufferSizeCallback(window, framebuffer_size_callback); | |
} | |
framebuffer_size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) | |
{ | |
using ctx := cast(^Context)glfw.GetWindowUserPointer(window); | |
framebuffer_resized = true; | |
} | |
init_vulkan :: proc(using ctx: ^Context, vertices: []Vertex, indices: []u16) | |
{ | |
context.user_ptr = &instance; | |
get_proc_address :: proc(p: rawptr, name: cstring) | |
{ | |
(cast(^rawptr)p)^ = glfw.GetInstanceProcAddress((^vk.Instance)(context.user_ptr)^, name); | |
} | |
vk.load_proc_addresses(get_proc_address); | |
create_instance(ctx); | |
vk.load_proc_addresses(get_proc_address); | |
extensions := get_extensions(); | |
for ext in &extensions do fmt.println(cstring(&ext.extensionName[0])); | |
create_surface(ctx); | |
get_suitable_device(ctx); | |
find_queue_families(ctx); | |
fmt.println("Queue Indices:"); | |
for q, f in queue_indices do fmt.printf(" %v: %d\n", f, q); | |
create_device(ctx); | |
for q, f in &queues | |
{ | |
vk.GetDeviceQueue(device, u32(queue_indices[f]), 0, &q); | |
} | |
create_swap_chain(ctx); | |
create_image_views(ctx); | |
create_graphics_pipeline(ctx, "shader.vert", "shader.frag"); | |
create_framebuffers(ctx); | |
create_command_pool(ctx); | |
create_vertex_buffer(ctx, vertices); | |
create_index_buffer(ctx, indices); | |
create_command_buffers(ctx); | |
create_sync_objects(ctx); | |
return; | |
} | |
deinit_vulkan :: proc(using ctx: ^Context) | |
{ | |
cleanup_swap_chain(ctx); | |
vk.FreeMemory(device, index_buffer.memory, nil); | |
vk.DestroyBuffer(device, index_buffer.buffer, nil); | |
vk.FreeMemory(device, vertex_buffer.memory, nil); | |
vk.DestroyBuffer(device, vertex_buffer.buffer, nil); | |
vk.DestroyPipeline(device, pipeline.handle, nil); | |
vk.DestroyPipelineLayout(device, pipeline.layout, nil); | |
vk.DestroyRenderPass(device, pipeline.render_pass, nil); | |
for i in 0..<MAX_FRAMES_IN_FLIGHT | |
{ | |
vk.DestroySemaphore(device, image_available[i], nil); | |
vk.DestroySemaphore(device, render_finished[i], nil); | |
vk.DestroyFence(device, in_flight[i], nil); | |
} | |
vk.DestroyCommandPool(device, command_pool, nil); | |
vk.DestroyDevice(device, nil); | |
vk.DestroySurfaceKHR(instance, surface, nil); | |
vk.DestroyInstance(instance, nil); | |
} | |
compile_shader :: proc(name: string, kind: shaderc.shader_kind) -> []u8 | |
{ | |
src_path := fmt.tprintf("./shaders/%s", name); | |
cmp_path := fmt.tprintf("./shaders/compiled/%s.spv", name); | |
src_time, src_err := os.last_write_time_by_name(src_path); | |
if (src_err != os.ERROR_NONE) | |
{ | |
fmt.eprintf("Failed to open shader %q\n", src_path); | |
return nil; | |
} | |
cmp_time, cmp_err := os.last_write_time_by_name(cmp_path); | |
if cmp_err == os.ERROR_NONE && cmp_time >= src_time | |
{ | |
code, _ := os.read_entire_file(cmp_path); | |
return code; | |
} | |
comp := shaderc.compiler_initialize(); | |
options := shaderc.compile_options_initialize(); | |
defer | |
{ | |
shaderc.compiler_release(comp); | |
shaderc.compile_options_release(options); | |
} | |
shaderc.compile_options_set_optimization_level(options, .performance); | |
code, _ := os.read_entire_file(src_path); | |
c_path := strings.clone_to_cstring(src_path, context.temp_allocator); | |
res := shaderc.compile_into_spv(comp, cstring(raw_data(code)), len(code), kind, c_path, cstring("main"), options); | |
defer shaderc.result_release(res); | |
status := shaderc.result_get_compilation_status(res); | |
if status != .success | |
{ | |
fmt.printf("%s: Error: %s\n", name, shaderc.result_get_error_message(res)); | |
return nil; | |
} | |
length := shaderc.result_get_length(res); | |
out := make([]u8, length); | |
c_out := shaderc.result_get_bytes(res); | |
mem.copy(raw_data(out), c_out, int(length)); | |
os.write_entire_file(cmp_path, out); | |
return out; | |
} | |
/* | |
compile_shader :: proc(path: string) -> []u8 | |
{ | |
code, ok := os.read_entire_file(path); | |
if !ok | |
{ | |
fmt.eprintf("Failed to open shader %q\n", path); | |
return nil; | |
} | |
defer delete(code); | |
input := glslang.input_t{ | |
language = glslang.SOURCE_GLSL, | |
stage = glslang.STAGE_VERTEX, | |
client = glslang.CLIENT_VULKAN, | |
client_version = glslang.TARGET_VULKAN_1_0, | |
target_language = glslang.TARGET_SPV, | |
target_language_version = glslang.TARGET_SPV_1_5, | |
code = cstring(&code[0]), | |
default_version = 100, | |
default_profile = glslang.CORE_PROFILE, | |
force_default_version_and_profile = 0, | |
forward_compatible = 0, | |
messages = glslang.MSG_DEFAULT_BIT, | |
}; | |
shader := glslang.shader_create(&input); | |
defer glslang.shader_delete(shader); | |
if res := glslang.shader_preprocess(shader, &input); res == 0 | |
{ | |
fmt.eprintf("GLSL preprocessing failed: %s\n", path); | |
fmt.eprintf("%s\n", glslang.shader_get_info_log(shader)); | |
fmt.eprintf("%s\n", glslang.shader_get_info_debug_log(shader)); | |
fmt.eprintf("%s\n", code); | |
} | |
return nil; | |
} | |
*/ | |
create_instance :: proc(using ctx: ^Context) | |
{ | |
app_info: vk.ApplicationInfo; | |
app_info.sType = .APPLICATION_INFO; | |
app_info.pApplicationName = "Hello Triangle"; | |
app_info.applicationVersion = vk.MAKE_VERSION(0, 0, 1); | |
app_info.pEngineName = "No Engine"; | |
app_info.engineVersion = vk.MAKE_VERSION(1, 0, 0); | |
app_info.apiVersion = vk.API_VERSION_1_0; | |
create_info: vk.InstanceCreateInfo; | |
create_info.sType = .INSTANCE_CREATE_INFO; | |
create_info.pApplicationInfo = &app_info; | |
glfw_ext := glfw.GetRequiredInstanceExtensions(); | |
create_info.ppEnabledExtensionNames = raw_data(glfw_ext); | |
create_info.enabledExtensionCount = cast(u32)len(glfw_ext); | |
when ODIN_DEBUG | |
{ | |
layer_count: u32; | |
vk.EnumerateInstanceLayerProperties(&layer_count, nil); | |
layers := make([]vk.LayerProperties, layer_count); | |
vk.EnumerateInstanceLayerProperties(&layer_count, raw_data(layers)); | |
outer: for name in VALIDATION_LAYERS | |
{ | |
for layer in &layers | |
{ | |
if name == cstring(&layer.layerName[0]) do continue outer; | |
} | |
fmt.eprintf("ERROR: validation layer %q not available\n", name); | |
os.exit(1); | |
} | |
create_info.ppEnabledLayerNames = &VALIDATION_LAYERS[0]; | |
create_info.enabledLayerCount = len(VALIDATION_LAYERS); | |
fmt.println("Validation Layers Loaded"); | |
} | |
else | |
{ | |
create_info.enabledLayerCount = 0; | |
} | |
if (vk.CreateInstance(&create_info, nil, &instance) != .SUCCESS) | |
{ | |
fmt.eprintf("ERROR: Failed to create instance\n"); | |
return; | |
} | |
fmt.println("Instance Created"); | |
} | |
get_extensions :: proc() -> []vk.ExtensionProperties | |
{ | |
n_ext: u32; | |
vk.EnumerateInstanceExtensionProperties(nil, &n_ext, nil); | |
extensions := make([]vk.ExtensionProperties, n_ext); | |
vk.EnumerateInstanceExtensionProperties(nil, &n_ext, raw_data(extensions)); | |
return extensions; | |
} | |
create_surface :: proc(using ctx: ^Context) | |
{ | |
surface_create_info := vk.Win32SurfaceCreateInfoKHR{}; | |
surface_create_info.sType= .WIN32_SURFACE_CREATE_INFO_KHR; | |
surface_create_info.hwnd = glfw.GetWin32Window(window); | |
surface_create_info.hinstance = cast(vk.HANDLE)windows.GetModuleHandleA(nil); | |
if res := glfw.CreateWindowSurface(instance, window, nil, &surface); res != .SUCCESS | |
{ | |
fmt.eprintf("ERROR: Failed to create window surface\n"); | |
os.exit(1); | |
} | |
} | |
check_device_extension_support :: proc(physical_device: vk.PhysicalDevice) -> bool | |
{ | |
ext_count: u32; | |
vk.EnumerateDeviceExtensionProperties(physical_device, nil, &ext_count, nil); | |
available_extensions := make([]vk.ExtensionProperties, ext_count); | |
vk.EnumerateDeviceExtensionProperties(physical_device, nil, &ext_count, raw_data(available_extensions)); | |
for ext in DEVICE_EXTENSIONS | |
{ | |
found: b32; | |
for available in &available_extensions | |
{ | |
if cstring(&available.extensionName[0]) == ext | |
{ | |
found = true; | |
break; | |
} | |
} | |
if !found do return false; | |
} | |
return true; | |
} | |
get_suitable_device :: proc(using ctx: ^Context) | |
{ | |
device_count: u32; | |
vk.EnumeratePhysicalDevices(instance, &device_count, nil); | |
if device_count == 0 | |
{ | |
fmt.eprintf("ERROR: Failed to find GPUs with Vulkan support\n"); | |
os.exit(1); | |
} | |
devices := make([]vk.PhysicalDevice, device_count); | |
vk.EnumeratePhysicalDevices(instance, &device_count, raw_data(devices)); | |
suitability :: proc(using ctx: ^Context, dev: vk.PhysicalDevice) -> int | |
{ | |
props: vk.PhysicalDeviceProperties; | |
features: vk.PhysicalDeviceFeatures; | |
vk.GetPhysicalDeviceProperties(dev, &props); | |
vk.GetPhysicalDeviceFeatures(dev, &features); | |
score := 0; | |
if props.deviceType == .DISCRETE_GPU do score += 1000; | |
score += cast(int)props.limits.maxImageDimension2D; | |
if !features.geometryShader do return 0; | |
if !check_device_extension_support(dev) do return 0; | |
query_swap_chain_details(ctx, dev); | |
if len(swap_chain.support.formats) == 0 || len(swap_chain.support.present_modes) == 0 do return 0; | |
return score; | |
} | |
hiscore := 0; | |
for dev in devices | |
{ | |
score := suitability(ctx, dev); | |
if score > hiscore | |
{ | |
physical_device = dev; | |
hiscore = score; | |
} | |
} | |
if (hiscore == 0) | |
{ | |
fmt.eprintf("ERROR: Failed to find a suitable GPU\n"); | |
os.exit(1); | |
} | |
} | |
find_queue_families :: proc(using ctx: ^Context) | |
{ | |
queue_count: u32; | |
vk.GetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_count, nil); | |
available_queues := make([]vk.QueueFamilyProperties, queue_count); | |
vk.GetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_count, raw_data(available_queues)); | |
for v, i in available_queues | |
{ | |
if .GRAPHICS in v.queueFlags && queue_indices[.Graphics] == -1 do queue_indices[.Graphics] = i; | |
present_support: b32; | |
vk.GetPhysicalDeviceSurfaceSupportKHR(physical_device, u32(i), surface, &present_support); | |
if present_support && queue_indices[.Present] == -1 do queue_indices[.Present] = i; | |
for q in queue_indices do if q == -1 do continue; | |
break; | |
} | |
} | |
create_device :: proc(using ctx: ^Context) | |
{ | |
unique_indices: map[int]b8; | |
defer delete(unique_indices); | |
for i in queue_indices do unique_indices[i] = true; | |
queue_priority := f32(1.0); | |
queue_create_infos: [dynamic]vk.DeviceQueueCreateInfo; | |
defer delete(queue_create_infos); | |
for k, _ in unique_indices | |
{ | |
queue_create_info: vk.DeviceQueueCreateInfo; | |
queue_create_info.sType = .DEVICE_QUEUE_CREATE_INFO; | |
queue_create_info.queueFamilyIndex = u32(queue_indices[.Graphics]); | |
queue_create_info.queueCount = 1; | |
queue_create_info.pQueuePriorities = &queue_priority; | |
append(&queue_create_infos, queue_create_info); | |
} | |
device_features: vk.PhysicalDeviceFeatures; | |
device_create_info: vk.DeviceCreateInfo; | |
device_create_info.sType = .DEVICE_CREATE_INFO; | |
device_create_info.enabledExtensionCount = u32(len(DEVICE_EXTENSIONS)); | |
device_create_info.ppEnabledExtensionNames = &DEVICE_EXTENSIONS[0]; | |
device_create_info.pQueueCreateInfos = raw_data(queue_create_infos); | |
device_create_info.queueCreateInfoCount = u32(len(queue_create_infos)); | |
device_create_info.pEnabledFeatures = &device_features; | |
device_create_info.enabledLayerCount = 0; | |
if vk.CreateDevice(physical_device, &device_create_info, nil, &device) != .SUCCESS | |
{ | |
fmt.eprintf("ERROR: Failed to create logical device\n"); | |
os.exit(1); | |
} | |
} | |
query_swap_chain_details :: proc(using ctx: ^Context, dev: vk.PhysicalDevice) | |
{ | |
vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surface, &swap_chain.support.capabilities); | |
format_count: u32; | |
vk.GetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &format_count, nil); | |
if format_count > 0 | |
{ | |
swap_chain.support.formats = make([]vk.SurfaceFormatKHR, format_count); | |
vk.GetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &format_count, raw_data(swap_chain.support.formats)); | |
} | |
present_mode_count: u32; | |
vk.GetPhysicalDeviceSurfacePresentModesKHR(dev, surface, &present_mode_count, nil); | |
if present_mode_count > 0 | |
{ | |
swap_chain.support.present_modes = make([]vk.PresentModeKHR, present_mode_count); | |
vk.GetPhysicalDeviceSurfacePresentModesKHR(dev, surface, &present_mode_count, raw_data(swap_chain.support.present_modes)); | |
} | |
} | |
choose_surface_format :: proc(using ctx: ^Context) -> vk.SurfaceFormatKHR | |
{ | |
for v in swap_chain.support.formats | |
{ | |
if v.format == .B8G8R8A8_SRGB && v.colorSpace == .SRGB_NONLINEAR do return v; | |
} | |
return swap_chain.support.formats[0]; | |
} | |
choose_present_mode :: proc(using ctx: ^Context) -> vk.PresentModeKHR | |
{ | |
for v in swap_chain.support.present_modes | |
{ | |
if v == .MAILBOX do return v; | |
} | |
return .FIFO; | |
} | |
choose_swap_extent :: proc(using ctx: ^Context) -> vk.Extent2D | |
{ | |
if (swap_chain.support.capabilities.currentExtent.width != max(u32)) | |
{ | |
return swap_chain.support.capabilities.currentExtent; | |
} | |
else | |
{ | |
width, height := glfw.GetFramebufferSize(window); | |
extent := vk.Extent2D{u32(width), u32(height)}; | |
extent.width = clamp(extent.width, swap_chain.support.capabilities.minImageExtent.width, swap_chain.support.capabilities.maxImageExtent.width); | |
extent.height = clamp(extent.height, swap_chain.support.capabilities.minImageExtent.height, swap_chain.support.capabilities.maxImageExtent.height); | |
return extent; | |
} | |
} | |
create_swap_chain :: proc(using ctx: ^Context) | |
{ | |
using ctx.swap_chain.support; | |
swap_chain.format = choose_surface_format(ctx); | |
swap_chain.present_mode = choose_present_mode(ctx); | |
swap_chain.extent = choose_swap_extent(ctx); | |
swap_chain.image_count = capabilities.minImageCount + 1; | |
if capabilities.maxImageCount > 0 && swap_chain.image_count > capabilities.maxImageCount | |
{ | |
swap_chain.image_count = capabilities.maxImageCount; | |
} | |
create_info: vk.SwapchainCreateInfoKHR; | |
create_info.sType = .SWAPCHAIN_CREATE_INFO_KHR; | |
create_info.surface = surface; | |
create_info.minImageCount = swap_chain.image_count; | |
create_info.imageFormat = swap_chain.format.format; | |
create_info.imageColorSpace = swap_chain.format.colorSpace; | |
create_info.imageExtent = swap_chain.extent; | |
create_info.imageArrayLayers = 1; | |
create_info.imageUsage = {.COLOR_ATTACHMENT}; | |
queue_family_indices := [len(QueueFamily)]u32{u32(queue_indices[.Graphics]), u32(queue_indices[.Present])}; | |
if queue_indices[.Graphics] != queue_indices[.Present] | |
{ | |
create_info.imageSharingMode = .CONCURRENT; | |
create_info.queueFamilyIndexCount = 2; | |
create_info.pQueueFamilyIndices = &queue_family_indices[0]; | |
} | |
else | |
{ | |
create_info.imageSharingMode = .EXCLUSIVE; | |
create_info.queueFamilyIndexCount = 0; | |
create_info.pQueueFamilyIndices = nil; | |
} | |
create_info.preTransform = capabilities.currentTransform; | |
create_info.compositeAlpha = {.OPAQUE}; | |
create_info.presentMode = swap_chain.present_mode; | |
create_info.clipped = true; | |
create_info.oldSwapchain = vk.SwapchainKHR{}; | |
if res := vk.CreateSwapchainKHR(device, &create_info, nil, &swap_chain.handle); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: failed to create swap chain!\n"); | |
os.exit(1); | |
} | |
vk.GetSwapchainImagesKHR(device, swap_chain.handle, &swap_chain.image_count, nil); | |
swap_chain.images = make([]vk.Image, swap_chain.image_count); | |
vk.GetSwapchainImagesKHR(device, swap_chain.handle, &swap_chain.image_count, raw_data(swap_chain.images)); | |
} | |
create_image_views :: proc(using ctx: ^Context) | |
{ | |
using ctx.swap_chain; | |
image_views = make([]vk.ImageView, len(images)); | |
for _, i in images | |
{ | |
create_info: vk.ImageViewCreateInfo; | |
create_info.sType = .IMAGE_VIEW_CREATE_INFO; | |
create_info.image = images[i]; | |
create_info.viewType = .D2; | |
create_info.format = format.format; | |
create_info.components.r = .IDENTITY; | |
create_info.components.g = .IDENTITY; | |
create_info.components.b = .IDENTITY; | |
create_info.components.a = .IDENTITY; | |
create_info.subresourceRange.aspectMask = {.COLOR}; | |
create_info.subresourceRange.baseMipLevel = 0; | |
create_info.subresourceRange.levelCount = 1; | |
create_info.subresourceRange.baseArrayLayer = 0; | |
create_info.subresourceRange.layerCount = 1; | |
if res := vk.CreateImageView(device, &create_info, nil, &image_views[i]); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: failed to create image view!"); | |
os.exit(1); | |
} | |
} | |
} | |
create_graphics_pipeline :: proc(using ctx: ^Context, vs_name: string, fs_name: string) | |
{ | |
vs_code := compile_shader(vs_name, .vertex_shader); | |
fs_code := compile_shader(fs_name, .fragment_shader); | |
/* | |
vs_code, vs_ok := os.read_entire_file(vs_path); | |
fs_code, fs_ok := os.read_entire_file(fs_path); | |
if !vs_ok | |
{ | |
fmt.eprintf("Error: could not load vertex shader %q\n", vs_path); | |
os.exit(1); | |
} | |
if !fs_ok | |
{ | |
fmt.eprintf("Error: could not load fragment shader %q\n", fs_path); | |
os.exit(1); | |
} | |
*/ | |
defer | |
{ | |
delete(vs_code); | |
delete(fs_code); | |
} | |
vs_shader := create_shader_module(ctx, vs_code); | |
fs_shader := create_shader_module(ctx, fs_code); | |
defer | |
{ | |
vk.DestroyShaderModule(device, vs_shader, nil); | |
vk.DestroyShaderModule(device, fs_shader, nil); | |
} | |
vs_info: vk.PipelineShaderStageCreateInfo; | |
vs_info.sType = .PIPELINE_SHADER_STAGE_CREATE_INFO; | |
vs_info.stage = {.VERTEX}; | |
vs_info.module = vs_shader; | |
vs_info.pName = "main"; | |
fs_info: vk.PipelineShaderStageCreateInfo; | |
fs_info.sType = .PIPELINE_SHADER_STAGE_CREATE_INFO; | |
fs_info.stage = {.FRAGMENT}; | |
fs_info.module = fs_shader; | |
fs_info.pName = "main"; | |
shader_stages := [?]vk.PipelineShaderStageCreateInfo{vs_info, fs_info}; | |
dynamic_states := [?]vk.DynamicState{.VIEWPORT, .SCISSOR}; | |
dynamic_state: vk.PipelineDynamicStateCreateInfo; | |
dynamic_state.sType = .PIPELINE_DYNAMIC_STATE_CREATE_INFO; | |
dynamic_state.dynamicStateCount = len(dynamic_states); | |
dynamic_state.pDynamicStates = &dynamic_states[0]; | |
vertex_input: vk.PipelineVertexInputStateCreateInfo; | |
vertex_input.sType = .PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; | |
vertex_input.vertexBindingDescriptionCount = 1; | |
vertex_input.pVertexBindingDescriptions = &VERTEX_BINDING; | |
vertex_input.vertexAttributeDescriptionCount = len(VERTEX_ATTRIBUTES); | |
vertex_input.pVertexAttributeDescriptions = &VERTEX_ATTRIBUTES[0]; | |
input_assembly: vk.PipelineInputAssemblyStateCreateInfo; | |
input_assembly.sType = .PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; | |
input_assembly.topology = .TRIANGLE_LIST; | |
input_assembly.primitiveRestartEnable = false; | |
viewport: vk.Viewport; | |
viewport.x = 0.0; | |
viewport.y = 0.0; | |
viewport.width = cast(f32)swap_chain.extent.width; | |
viewport.height = cast(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; | |
viewport_state: vk.PipelineViewportStateCreateInfo; | |
viewport_state.sType = .PIPELINE_VIEWPORT_STATE_CREATE_INFO; | |
viewport_state.viewportCount = 1; | |
viewport_state.scissorCount = 1; | |
rasterizer: vk.PipelineRasterizationStateCreateInfo; | |
rasterizer.sType = .PIPELINE_RASTERIZATION_STATE_CREATE_INFO; | |
rasterizer.depthClampEnable = false; | |
rasterizer.rasterizerDiscardEnable = false; | |
rasterizer.polygonMode = .FILL; | |
rasterizer.lineWidth = 1.0; | |
rasterizer.cullMode = {.BACK}; | |
rasterizer.frontFace = .CLOCKWISE; | |
rasterizer.depthBiasEnable = false; | |
rasterizer.depthBiasConstantFactor = 0.0; | |
rasterizer.depthBiasClamp = 0.0; | |
rasterizer.depthBiasSlopeFactor = 0.0; | |
multisampling: vk.PipelineMultisampleStateCreateInfo; | |
multisampling.sType = .PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; | |
multisampling.sampleShadingEnable = false; | |
multisampling.rasterizationSamples = {._1}; | |
multisampling.minSampleShading = 1.0; | |
multisampling.pSampleMask = nil; | |
multisampling.alphaToCoverageEnable = false; | |
multisampling.alphaToOneEnable = false; | |
color_blend_attachment: vk.PipelineColorBlendAttachmentState; | |
color_blend_attachment.colorWriteMask = {.R, .G, .B, .A}; | |
color_blend_attachment.blendEnable = true; | |
color_blend_attachment.srcColorBlendFactor = .SRC_ALPHA; | |
color_blend_attachment.dstColorBlendFactor = .ONE_MINUS_SRC_ALPHA; | |
color_blend_attachment.colorBlendOp = .ADD; | |
color_blend_attachment.srcAlphaBlendFactor = .ONE; | |
color_blend_attachment.dstAlphaBlendFactor = .ZERO; | |
color_blend_attachment.alphaBlendOp = .ADD; | |
color_blending: vk.PipelineColorBlendStateCreateInfo; | |
color_blending.sType = .PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; | |
color_blending.logicOpEnable = false; | |
color_blending.logicOp = .COPY; | |
color_blending.attachmentCount = 1; | |
color_blending.pAttachments = &color_blend_attachment; | |
color_blending.blendConstants[0] = 0.0; | |
color_blending.blendConstants[1] = 0.0; | |
color_blending.blendConstants[2] = 0.0; | |
color_blending.blendConstants[3] = 0.0; | |
pipeline_layout_info: vk.PipelineLayoutCreateInfo; | |
pipeline_layout_info.sType = .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 res := vk.CreatePipelineLayout(device, &pipeline_layout_info, nil, &pipeline.layout); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create pipeline layout!\n"); | |
os.exit(1); | |
} | |
create_render_pass(ctx); | |
pipeline_info: vk.GraphicsPipelineCreateInfo; | |
pipeline_info.sType = .GRAPHICS_PIPELINE_CREATE_INFO; | |
pipeline_info.stageCount = 2; | |
pipeline_info.pStages = &shader_stages[0]; | |
pipeline_info.pVertexInputState = &vertex_input; | |
pipeline_info.pInputAssemblyState = &input_assembly; | |
pipeline_info.pViewportState = &viewport_state; | |
pipeline_info.pRasterizationState = &rasterizer; | |
pipeline_info.pMultisampleState = &multisampling; | |
pipeline_info.pDepthStencilState = nil; | |
pipeline_info.pColorBlendState = &color_blending; | |
pipeline_info.pDynamicState = &dynamic_state; | |
pipeline_info.layout = pipeline.layout; | |
pipeline_info.renderPass = pipeline.render_pass; | |
pipeline_info.subpass = 0; | |
pipeline_info.basePipelineHandle = vk.Pipeline{}; | |
pipeline_info.basePipelineIndex = -1; | |
if res := vk.CreateGraphicsPipelines(device, 0, 1, &pipeline_info, nil, &pipeline.handle); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create graphics pipeline!\n"); | |
os.exit(1); | |
} | |
} | |
create_shader_module :: proc(using ctx: ^Context, code: []u8) -> vk.ShaderModule | |
{ | |
create_info: vk.ShaderModuleCreateInfo; | |
create_info.sType = .SHADER_MODULE_CREATE_INFO; | |
create_info.codeSize = len(code); | |
create_info.pCode = cast(^u32)raw_data(code); | |
shader: vk.ShaderModule; | |
if res := vk.CreateShaderModule(device, &create_info, nil, &shader); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Could not create shader module!\n"); | |
os.exit(1); | |
} | |
return shader; | |
} | |
create_render_pass :: proc(using ctx: ^Context) | |
{ | |
color_attachment: vk.AttachmentDescription; | |
color_attachment.format = swap_chain.format.format; | |
color_attachment.samples = {._1}; | |
color_attachment.loadOp = .CLEAR; | |
color_attachment.storeOp = .STORE; | |
color_attachment.stencilLoadOp = .DONT_CARE; | |
color_attachment.stencilStoreOp = .DONT_CARE; | |
color_attachment.initialLayout = .UNDEFINED; | |
color_attachment.finalLayout = .PRESENT_SRC_KHR; | |
color_attachment_ref: vk.AttachmentReference; | |
color_attachment_ref.attachment = 0; | |
color_attachment_ref.layout = .COLOR_ATTACHMENT_OPTIMAL; | |
subpass: vk.SubpassDescription; | |
subpass.pipelineBindPoint = .GRAPHICS; | |
subpass.colorAttachmentCount = 1; | |
subpass.pColorAttachments = &color_attachment_ref; | |
dependency: vk.SubpassDependency; | |
dependency.srcSubpass = vk.SUBPASS_EXTERNAL; | |
dependency.dstSubpass = 0; | |
dependency.srcStageMask = {.COLOR_ATTACHMENT_OUTPUT}; | |
dependency.srcAccessMask = {}; | |
dependency.dstStageMask = {.COLOR_ATTACHMENT_OUTPUT}; | |
dependency.dstAccessMask = {.COLOR_ATTACHMENT_WRITE}; | |
render_pass_info: vk.RenderPassCreateInfo; | |
render_pass_info.sType = .RENDER_PASS_CREATE_INFO; | |
render_pass_info.attachmentCount = 1; | |
render_pass_info.pAttachments = &color_attachment; | |
render_pass_info.subpassCount = 1; | |
render_pass_info.pSubpasses = &subpass; | |
render_pass_info.dependencyCount = 1; | |
render_pass_info.pDependencies = &dependency; | |
if res := vk.CreateRenderPass(device, &render_pass_info, nil, &pipeline.render_pass); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create render pass!\n"); | |
os.exit(1); | |
} | |
} | |
create_framebuffers :: proc(using ctx: ^Context) | |
{ | |
swap_chain.framebuffers = make([]vk.Framebuffer, len(swap_chain.image_views)); | |
for v, i in swap_chain.image_views | |
{ | |
attachments := [?]vk.ImageView{v}; | |
framebuffer_info: vk.FramebufferCreateInfo; | |
framebuffer_info.sType = .FRAMEBUFFER_CREATE_INFO; | |
framebuffer_info.renderPass = pipeline.render_pass; | |
framebuffer_info.attachmentCount = 1; | |
framebuffer_info.pAttachments = &attachments[0]; | |
framebuffer_info.width = swap_chain.extent.width; | |
framebuffer_info.height = swap_chain.extent.height; | |
framebuffer_info.layers = 1; | |
if res := vk.CreateFramebuffer(device, &framebuffer_info, nil, &swap_chain.framebuffers[i]); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create framebuffer #%d!\n", i); | |
os.exit(1); | |
} | |
} | |
} | |
create_command_pool :: proc(using ctx: ^Context) | |
{ | |
pool_info: vk.CommandPoolCreateInfo; | |
pool_info.sType = .COMMAND_POOL_CREATE_INFO; | |
pool_info.flags = {.RESET_COMMAND_BUFFER}; | |
pool_info.queueFamilyIndex = u32(queue_indices[.Graphics]); | |
if res := vk.CreateCommandPool(device, &pool_info, nil, &command_pool); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create command pool!\n"); | |
os.exit(1); | |
} | |
} | |
create_command_buffers :: proc(using ctx: ^Context) | |
{ | |
alloc_info: vk.CommandBufferAllocateInfo; | |
alloc_info.sType = .COMMAND_BUFFER_ALLOCATE_INFO; | |
alloc_info.commandPool = command_pool; | |
alloc_info.level = .PRIMARY; | |
alloc_info.commandBufferCount = len(command_buffers); | |
if res := vk.AllocateCommandBuffers(device, &alloc_info, &command_buffers[0]); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to allocate command buffers!\n"); | |
os.exit(1); | |
} | |
} | |
record_command_buffer :: proc(using ctx: ^Context, buffer: vk.CommandBuffer, image_index: u32) | |
{ | |
begin_info: vk.CommandBufferBeginInfo; | |
begin_info.sType = .COMMAND_BUFFER_BEGIN_INFO; | |
begin_info.flags = {}; | |
begin_info.pInheritanceInfo = nil; | |
if res := vk.BeginCommandBuffer(buffer, &begin_info); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to begin recording command buffer!\n"); | |
os.exit(1); | |
} | |
render_pass_info: vk.RenderPassBeginInfo; | |
render_pass_info.sType = .RENDER_PASS_BEGIN_INFO; | |
render_pass_info.renderPass = pipeline.render_pass; | |
render_pass_info.framebuffer = swap_chain.framebuffers[image_index]; | |
render_pass_info.renderArea.offset = {0, 0}; | |
render_pass_info.renderArea.extent = swap_chain.extent; | |
clear_color: vk.ClearValue; | |
clear_color.color.float32 = [4]f32{0.0, 0.0, 0.0, 1.0}; | |
render_pass_info.clearValueCount = 1; | |
render_pass_info.pClearValues = &clear_color; | |
vk.CmdBeginRenderPass(buffer, &render_pass_info, .INLINE); | |
vk.CmdBindPipeline(buffer, .GRAPHICS, pipeline.handle); | |
vertex_buffers := [?]vk.Buffer{vertex_buffer.buffer}; | |
offsets := [?]vk.DeviceSize{0}; | |
vk.CmdBindVertexBuffers(buffer, 0, 1, &vertex_buffers[0], &offsets[0]); | |
vk.CmdBindIndexBuffer(buffer, index_buffer.buffer, 0, .UINT16); | |
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(buffer, 0, 1, &viewport); | |
scissor: vk.Rect2D; | |
scissor.offset = {0, 0}; | |
scissor.extent = swap_chain.extent; | |
vk.CmdSetScissor(buffer, 0, 1, &scissor); | |
vk.CmdDrawIndexed(buffer, cast(u32)index_buffer.length, 1, 0, 0, 0); | |
vk.CmdEndRenderPass(buffer); | |
if res := vk.EndCommandBuffer(buffer); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to record command buffer!\n"); | |
os.exit(1); | |
} | |
} | |
create_sync_objects :: proc(using ctx: ^Context) | |
{ | |
semaphore_info: vk.SemaphoreCreateInfo; | |
semaphore_info.sType = .SEMAPHORE_CREATE_INFO; | |
fence_info: vk.FenceCreateInfo; | |
fence_info.sType = .FENCE_CREATE_INFO; | |
fence_info.flags = {.SIGNALED} | |
for i in 0..<MAX_FRAMES_IN_FLIGHT | |
{ | |
res := vk.CreateSemaphore(device, &semaphore_info, nil, &image_available[i]); | |
if res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create \"image_available\" semaphore\n"); | |
os.exit(1); | |
} | |
res = vk.CreateSemaphore(device, &semaphore_info, nil, &render_finished[i]); | |
if res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create \"render_finished\" semaphore\n"); | |
os.exit(1); | |
} | |
res = vk.CreateFence(device, &fence_info, nil, &in_flight[i]); | |
if res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to create \"in_flight\" fence\n"); | |
os.exit(1); | |
} | |
} | |
} | |
recreate_swap_chain :: proc(using ctx: ^Context) | |
{ | |
width, height := glfw.GetFramebufferSize(window); | |
for width == 0 && height == 0 | |
{ | |
width, height = glfw.GetFramebufferSize(window); | |
glfw.WaitEvents(); | |
} | |
vk.DeviceWaitIdle(device); | |
cleanup_swap_chain(ctx); | |
create_swap_chain(ctx); | |
create_image_views(ctx); | |
create_framebuffers(ctx); | |
} | |
cleanup_swap_chain :: proc(using ctx: ^Context) | |
{ | |
for f in swap_chain.framebuffers | |
{ | |
vk.DestroyFramebuffer(device, f, nil); | |
} | |
for view in swap_chain.image_views | |
{ | |
vk.DestroyImageView(device, view, nil); | |
} | |
vk.DestroySwapchainKHR(device, swap_chain.handle, nil); | |
} | |
create_vertex_buffer :: proc(using ctx: ^Context, vertices: []Vertex) | |
{ | |
vertex_buffer.length = len(vertices); | |
vertex_buffer.size = cast(vk.DeviceSize)(len(vertices) * size_of(Vertex)); | |
staging: Buffer; | |
create_buffer(ctx, size_of(Vertex), len(vertices), {.TRANSFER_SRC}, {.HOST_VISIBLE, .HOST_COHERENT}, &staging); | |
data: rawptr; | |
vk.MapMemory(device, staging.memory, 0, vertex_buffer.size, {}, &data); | |
mem.copy(data, raw_data(vertices), cast(int)vertex_buffer.size); | |
vk.UnmapMemory(device, staging.memory); | |
create_buffer(ctx, size_of(Vertex), len(vertices), {.VERTEX_BUFFER, .TRANSFER_DST}, {.DEVICE_LOCAL}, &vertex_buffer); | |
copy_buffer(ctx, staging, vertex_buffer, vertex_buffer.size); | |
vk.FreeMemory(device, staging.memory, nil); | |
vk.DestroyBuffer(device, staging.buffer, nil); | |
} | |
create_index_buffer :: proc(using ctx: ^Context, indices: []u16) | |
{ | |
index_buffer.length = len(indices); | |
index_buffer.size = cast(vk.DeviceSize)(len(indices) * size_of(indices[0])); | |
staging: Buffer; | |
create_buffer(ctx, size_of(indices[0]), len(indices), {.TRANSFER_SRC}, {.HOST_VISIBLE, .HOST_COHERENT}, &staging); | |
data: rawptr; | |
vk.MapMemory(device, staging.memory, 0, index_buffer.size, {}, &data); | |
mem.copy(data, raw_data(indices), cast(int)index_buffer.size); | |
vk.UnmapMemory(device, staging.memory); | |
create_buffer(ctx, size_of(Vertex), len(indices), {.INDEX_BUFFER, .TRANSFER_DST}, {.DEVICE_LOCAL}, &index_buffer); | |
copy_buffer(ctx, staging, index_buffer, index_buffer.size); | |
vk.FreeMemory(device, staging.memory, nil); | |
vk.DestroyBuffer(device, staging.buffer, nil); | |
} | |
copy_buffer :: proc(using ctx: ^Context, src, dst: Buffer, size: vk.DeviceSize) | |
{ | |
alloc_info := vk.CommandBufferAllocateInfo{ | |
sType = .COMMAND_BUFFER_ALLOCATE_INFO, | |
level = .PRIMARY, | |
commandPool = command_pool, | |
commandBufferCount = 1, | |
}; | |
cmd_buffer: vk.CommandBuffer; | |
vk.AllocateCommandBuffers(device, &alloc_info, &cmd_buffer); | |
begin_info := vk.CommandBufferBeginInfo{ | |
sType = .COMMAND_BUFFER_BEGIN_INFO, | |
flags = {.ONE_TIME_SUBMIT}, | |
} | |
vk.BeginCommandBuffer(cmd_buffer, &begin_info); | |
copy_region := vk.BufferCopy{ | |
srcOffset = 0, | |
dstOffset = 0, | |
size = size, | |
} | |
vk.CmdCopyBuffer(cmd_buffer, src.buffer, dst.buffer, 1, ©_region); | |
vk.EndCommandBuffer(cmd_buffer); | |
submit_info := vk.SubmitInfo{ | |
sType = .SUBMIT_INFO, | |
commandBufferCount = 1, | |
pCommandBuffers = &cmd_buffer, | |
}; | |
vk.QueueSubmit(queues[.Graphics], 1, &submit_info, {}); | |
vk.QueueWaitIdle(queues[.Graphics]); | |
vk.FreeCommandBuffers(device, command_pool, 1, &cmd_buffer); | |
} | |
find_memory_type :: proc(using ctx: ^Context, type_filter: u32, properties: vk.MemoryPropertyFlags) -> u32 | |
{ | |
mem_properties: vk.PhysicalDeviceMemoryProperties; | |
vk.GetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); | |
for i in 0..<mem_properties.memoryTypeCount | |
{ | |
if (type_filter & (1 << i) != 0) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties | |
{ | |
return i; | |
} | |
} | |
fmt.eprintf("Error: Failed to find suitable memory type!\n"); | |
os.exit(1); | |
} | |
create_buffer :: proc(using ctx: ^Context, member_size: int, count: int, usage: vk.BufferUsageFlags, properties: vk.MemoryPropertyFlags, buffer: ^Buffer) | |
{ | |
buffer_info := vk.BufferCreateInfo{ | |
sType = .BUFFER_CREATE_INFO, | |
size = cast(vk.DeviceSize)(member_size * count), | |
usage = usage, | |
sharingMode = .EXCLUSIVE, | |
}; | |
if res := vk.CreateBuffer(device, &buffer_info, nil, &buffer.buffer); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: failed to create buffer\n"); | |
os.exit(1); | |
} | |
mem_requirements: vk.MemoryRequirements; | |
vk.GetBufferMemoryRequirements(device, buffer.buffer, &mem_requirements); | |
alloc_info := vk.MemoryAllocateInfo{ | |
sType = .MEMORY_ALLOCATE_INFO, | |
allocationSize = mem_requirements.size, | |
memoryTypeIndex = find_memory_type(ctx, mem_requirements.memoryTypeBits, {.HOST_VISIBLE, .HOST_COHERENT}) | |
}; | |
if res := vk.AllocateMemory(device, &alloc_info, nil, &buffer.memory); res != .SUCCESS | |
{ | |
fmt.eprintf("Error: Failed to allocate buffer memory!\n"); | |
os.exit(1); | |
} | |
vk.BindBufferMemory(device, buffer.buffer, buffer.memory, 0); | |
} |
This file contains 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
#version 450 | |
layout(location = 0) in vec3 fragColor; | |
layout(location = 0) out vec4 outColor; | |
void main() | |
{ | |
outColor = vec4(fragColor, 1.0); | |
} |
This file contains 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
#version 450 | |
layout(location = 0) in vec2 in_position; | |
layout(location = 1) in vec3 in_color; | |
layout(location = 0) out vec3 fragColor; | |
void main() | |
{ | |
gl_Position = vec4(in_position, 0.0, 1.0); | |
fragColor = in_color; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment