const std = @import("std"); const glfw = @import("zglfw"); const vk = @import("vulkan"); const square_vert_spv = @embedFile("square_vertex_shader"); const square_frag_spv = @embedFile("square_fragment_shader"); pub fn main() !void { std.debug.print("zig-chess bootstrap\n", .{}); std.debug.print("vertex shader bytes: {}\n", .{square_vert_spv.len}); std.debug.print("fragment shader bytes: {}\n", .{square_frag_spv.len}); try glfw.init(); defer glfw.terminate(); glfw.windowHint(.client_api, .no_api); const window = try glfw.Window.create( 800, 600, "zig-chess", null, null, ); defer window.destroy(); std.debug.print("GLFW platform: {any}\n", .{glfw.getPlatform()}); std.debug.print("Vulkan supported by GLFW: {}\n", .{glfw.isVulkanSupported()}); const size = window.getSize(); const fb_size = window.getFramebufferSize(); std.debug.print("window size: {}x{}\n", .{ size[0], size[1] }); std.debug.print("framebuffer size: {}x{}\n", .{ fb_size[0], fb_size[1] }); std.debug.print("window visible attr: {}\n", .{window.getAttribute(.visible)}); window.show(); window.requestAttention(); const base = vk.BaseWrapper.load(glfw.getInstanceProcAddress); const required_extensions = try glfw.getRequiredInstanceExtensions(); const app_info = vk.ApplicationInfo{ .p_application_name = "zig-chess", .application_version = 1, .p_engine_name = "zig-chess", .engine_version = 1, .api_version = @bitCast(vk.makeApiVersion(0, 1, 2, 0)), }; const instance_create_info = vk.InstanceCreateInfo{ .p_application_info = &app_info, .enabled_extension_count = @intCast(required_extensions.len), .pp_enabled_extension_names = required_extensions.ptr, }; const instance = try base.createInstance(&instance_create_info, null); std.debug.print("required instance extensions:\n", .{}); for (required_extensions) |extension| { std.debug.print(" {s}\n", .{extension}); } std.debug.print("Created Vulkan Instance\n", .{}); const vki = vk.InstanceWrapper.load(instance, base.dispatch.vkGetInstanceProcAddr.?); defer vki.destroyInstance(instance, null); var surface: vk.SurfaceKHR = undefined; try glfw.createWindowSurface(instance, window, null, &surface); defer vki.destroySurfaceKHR(instance, surface, null); std.debug.print("Created Vulkan surface\n", .{}); const physical_devices = try vki.enumeratePhysicalDevicesAlloc(instance, std.heap.page_allocator); defer std.heap.page_allocator.free(physical_devices); std.debug.print("physical devices: {}\n", .{physical_devices.len}); for (physical_devices, 0..) |physical_device, i| { const props = vki.getPhysicalDeviceProperties(physical_device); std.debug.print("device {}: {s}\n", .{ i, std.mem.sliceTo(&props.device_name, 0) }); const queue_families = try vki.getPhysicalDeviceQueueFamilyPropertiesAlloc( physical_device, std.heap.page_allocator, ); defer std.heap.page_allocator.free(queue_families); for (queue_families, 0..) |queue_family, queue_index| { const supports_graphics = queue_family.queue_flags.graphics_bit; const supports_compute = queue_family.queue_flags.compute_bit; const supports_transfer = queue_family.queue_flags.transfer_bit; const supports_present = try vki.getPhysicalDeviceSurfaceSupportKHR( physical_device, @intCast(queue_index), surface, ); std.debug.print( " queue {}: count={}, graphics={}, compute={}, transfer={}, present={}\n", .{ queue_index, queue_family.queue_count, supports_graphics, supports_compute, supports_transfer, supports_present, }, ); } } const selected_physical_device = physical_devices[1]; const graphics_queue_family_index: u32 = 0; const selected_props = vki.getPhysicalDeviceProperties(selected_physical_device); std.debug.print( "selected device: {s}, queue family {}\n", .{ std.mem.sliceTo(&selected_props.device_name, 0), graphics_queue_family_index, }, ); const queue_priority: f32 = 1.0; const queue_create_info = vk.DeviceQueueCreateInfo{ .queue_family_index = graphics_queue_family_index, .queue_count = 1, .p_queue_priorities = @ptrCast(&queue_priority), }; const device_extensions = [_][*:0]const u8{ "VK_KHR_swapchain", }; const device_create_info = vk.DeviceCreateInfo{ .queue_create_info_count = 1, .p_queue_create_infos = @ptrCast(&queue_create_info), .enabled_extension_count = device_extensions.len, .pp_enabled_extension_names = &device_extensions, }; const device = try vki.createDevice(selected_physical_device, &device_create_info, null); std.debug.print("created logical device\n", .{}); const vkd = vk.DeviceWrapper.load(device, vki.dispatch.vkGetDeviceProcAddr.?); defer vkd.destroyDevice(device, null); const graphics_queue = vkd.getDeviceQueue(device, graphics_queue_family_index, 0); std.debug.print("retrieved graphics queue\n", .{}); const surface_caps = try vki.getPhysicalDeviceSurfaceCapabilitiesKHR( selected_physical_device, surface, ); std.debug.print( "surface current extent: {}x{}\n", .{ surface_caps.current_extent.width, surface_caps.current_extent.height, }, ); std.debug.print( "surface min/max image count: {}/{}\n", .{ surface_caps.min_image_count, surface_caps.max_image_count, }, ); const surface_formats = try vki.getPhysicalDeviceSurfaceFormatsAllocKHR( selected_physical_device, surface, std.heap.page_allocator, ); defer std.heap.page_allocator.free(surface_formats); std.debug.print("surface formats: {}\n", .{surface_formats.len}); for (surface_formats, 0..) |format, i| { std.debug.print( " format {}: format={any}, color_space={any}\n", .{ i, format.format, format.color_space }, ); } const present_modes = try vki.getPhysicalDeviceSurfacePresentModesAllocKHR( selected_physical_device, surface, std.heap.page_allocator, ); defer std.heap.page_allocator.free(present_modes); std.debug.print("present modes: {}\n", .{present_modes.len}); for (present_modes, 0..) |mode, i| { std.debug.print(" present mode {}: {any}\n", .{ i, mode }); } var chosen_surface_format = surface_formats[0]; for (surface_formats) |format| { if (format.format == .b8g8r8a8_srgb and format.color_space == .srgb_nonlinear_khr) { chosen_surface_format = format; break; } } var chosen_present_mode: vk.PresentModeKHR = .fifo_khr; for (present_modes) |mode| { if (mode == .fifo_khr) { chosen_present_mode = mode; break; } } const framebuffer_size = window.getFramebufferSize(); const chosen_extent = if (surface_caps.current_extent.width != std.math.maxInt(u32)) surface_caps.current_extent else vk.Extent2D{ .width = @intCast(framebuffer_size[0]), .height = @intCast(framebuffer_size[1]), }; var chosen_image_count = surface_caps.min_image_count + 1; if (surface_caps.max_image_count != 0 and chosen_image_count > surface_caps.max_image_count) { chosen_image_count = surface_caps.max_image_count; } std.debug.print( "chosen swapchain format={any}, color_space={any}\n", .{ chosen_surface_format.format, chosen_surface_format.color_space }, ); std.debug.print("chosen present mode={any}\n", .{chosen_present_mode}); std.debug.print( "chosen extent={}x{}\n", .{ chosen_extent.width, chosen_extent.height }, ); std.debug.print("chosen image count={}\n", .{chosen_image_count}); const swapchain_create_info = vk.SwapchainCreateInfoKHR{ .surface = surface, .min_image_count = chosen_image_count, .image_format = chosen_surface_format.format, .image_color_space = chosen_surface_format.color_space, .image_extent = chosen_extent, .image_array_layers = 1, .image_usage = .{ .color_attachment_bit = true, }, .image_sharing_mode = .exclusive, .pre_transform = surface_caps.current_transform, .composite_alpha = .{ .opaque_bit_khr = true, }, .present_mode = chosen_present_mode, .clipped = .true, }; const swapchain = try vkd.createSwapchainKHR(device, &swapchain_create_info, null); defer vkd.destroySwapchainKHR(device, swapchain, null); std.debug.print("created swapchain\n", .{}); const swapchain_images = try vkd.getSwapchainImagesAllocKHR( device, swapchain, std.heap.page_allocator, ); defer std.heap.page_allocator.free(swapchain_images); std.debug.print("swapchain images: {}\n", .{swapchain_images.len}); const swapchain_image_views = try std.heap.page_allocator.alloc( vk.ImageView, swapchain_images.len, ); defer std.heap.page_allocator.free(swapchain_image_views); for (swapchain_images, 0..) |image, i| { const image_view_create_info = vk.ImageViewCreateInfo{ .image = image, .view_type = .@"2d", .format = chosen_surface_format.format, .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity, }, .subresource_range = .{ .aspect_mask = .{ .color_bit = true, }, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 1, }, }; swapchain_image_views[i] = try vkd.createImageView( device, &image_view_create_info, null, ); } defer { for (swapchain_image_views) |image_view| { vkd.destroyImageView(device, image_view, null); } } std.debug.print("created swapchain image views: {}\n", .{swapchain_image_views.len}); const color_attachment = vk.AttachmentDescription{ .format = chosen_surface_format.format, .samples = .{ .@"1_bit" = true }, .load_op = .clear, .store_op = .store, .stencil_load_op = .dont_care, .stencil_store_op = .dont_care, .initial_layout = .undefined, .final_layout = .present_src_khr, }; const color_attachment_ref = vk.AttachmentReference{ .attachment = 0, .layout = .color_attachment_optimal, }; const subpass = vk.SubpassDescription{ .pipeline_bind_point = .graphics, .color_attachment_count = 1, .p_color_attachments = @ptrCast(&color_attachment_ref), }; const subpass_dependency = vk.SubpassDependency{ .src_subpass = vk.SUBPASS_EXTERNAL, .dst_subpass = 0, .src_stage_mask = .{ .color_attachment_output_bit = true, }, .src_access_mask = .{}, .dst_stage_mask = .{ .color_attachment_output_bit = true, }, .dst_access_mask = .{ .color_attachment_write_bit = true, }, }; const render_pass_create_info = vk.RenderPassCreateInfo{ .attachment_count = 1, .p_attachments = @ptrCast(&color_attachment), .subpass_count = 1, .p_subpasses = @ptrCast(&subpass), .dependency_count = 1, .p_dependencies = @ptrCast(&subpass_dependency), }; const render_pass = try vkd.createRenderPass(device, &render_pass_create_info, null); defer vkd.destroyRenderPass(device, render_pass, null); std.debug.print("created render pass\n", .{}); const framebuffers = try std.heap.page_allocator.alloc( vk.Framebuffer, swapchain_image_views.len, ); defer std.heap.page_allocator.free(framebuffers); for (swapchain_image_views, 0..) |image_view, i| { const attachments = [_]vk.ImageView{image_view}; const framebuffer_create_info = vk.FramebufferCreateInfo{ .render_pass = render_pass, .attachment_count = attachments.len, .p_attachments = &attachments, .width = chosen_extent.width, .height = chosen_extent.height, .layers = 1, }; framebuffers[i] = try vkd.createFramebuffer( device, &framebuffer_create_info, null, ); } defer { for (framebuffers) |framebuffer| { vkd.destroyFramebuffer(device, framebuffer, null); } } std.debug.print("created framebuffers: {}\n", .{framebuffers.len}); const command_pool_create_info = vk.CommandPoolCreateInfo{ .flags = .{ .reset_command_buffer_bit = true, }, .queue_family_index = graphics_queue_family_index, }; const command_pool = try vkd.createCommandPool( device, &command_pool_create_info, null, ); defer vkd.destroyCommandPool(device, command_pool, null); std.debug.print("created command pool\n", .{}); const command_buffers = try std.heap.page_allocator.alloc( vk.CommandBuffer, framebuffers.len, ); defer std.heap.page_allocator.free(command_buffers); const command_buffer_allocate_info = vk.CommandBufferAllocateInfo{ .command_pool = command_pool, .level = .primary, .command_buffer_count = @intCast(command_buffers.len), }; try vkd.allocateCommandBuffers( device, &command_buffer_allocate_info, command_buffers.ptr, ); std.debug.print("allocated command buffers: {}\n", .{command_buffers.len}); for (command_buffers, 0..) |command_buffer, i| { const begin_info = vk.CommandBufferBeginInfo{}; try vkd.beginCommandBuffer(command_buffer, &begin_info); const clear_color = vk.ClearValue{ .color = .{ .float_32 = .{ 0.02, 0.02, 0.08, 1.0 }, }, }; const render_pass_begin_info = vk.RenderPassBeginInfo{ .render_pass = render_pass, .framebuffer = framebuffers[i], .render_area = .{ .offset = .{ .x = 0, .y = 0 }, .extent = chosen_extent, }, .clear_value_count = 1, .p_clear_values = @ptrCast(&clear_color), }; vkd.cmdBeginRenderPass( command_buffer, &render_pass_begin_info, .@"inline", ); vkd.cmdEndRenderPass(command_buffer); try vkd.endCommandBuffer(command_buffer); } std.debug.print("recorded command buffers\n", .{}); const semaphore_create_info = vk.SemaphoreCreateInfo{}; const image_available_semaphore = try vkd.createSemaphore( device, &semaphore_create_info, null, ); defer vkd.destroySemaphore(device, image_available_semaphore, null); const render_finished_semaphore = try vkd.createSemaphore( device, &semaphore_create_info, null, ); defer vkd.destroySemaphore(device, render_finished_semaphore, null); const fence_create_info = vk.FenceCreateInfo{ .flags = .{ .signaled_bit = true, }, }; const in_flight_fence = try vkd.createFence( device, &fence_create_info, null, ); defer vkd.destroyFence(device, in_flight_fence, null); std.debug.print("created synchronization objects\n", .{}); const wait_fences = [_]vk.Fence{in_flight_fence}; _ = try vkd.waitForFences(device, &wait_fences, .true, std.math.maxInt(u64)); try vkd.resetFences(device, &wait_fences); const acquire_result = try vkd.acquireNextImageKHR( device, swapchain, std.math.maxInt(u64), image_available_semaphore, .null_handle, ); const image_index = acquire_result.image_index; std.debug.print("acquired swapchain image: {}\n", .{image_index}); const wait_semaphores = [_]vk.Semaphore{image_available_semaphore}; const wait_stages = [_]vk.PipelineStageFlags{ .{ .color_attachment_output_bit = true, }, }; const signal_semaphores = [_]vk.Semaphore{render_finished_semaphore}; const submit_command_buffers = [_]vk.CommandBuffer{ command_buffers[image_index], }; const submit_info = vk.SubmitInfo{ .wait_semaphore_count = wait_semaphores.len, .p_wait_semaphores = &wait_semaphores, .p_wait_dst_stage_mask = &wait_stages, .command_buffer_count = submit_command_buffers.len, .p_command_buffers = &submit_command_buffers, .signal_semaphore_count = signal_semaphores.len, .p_signal_semaphores = &signal_semaphores, }; try vkd.queueSubmit(graphics_queue, &[_]vk.SubmitInfo{submit_info}, in_flight_fence); const present_swapchains = [_]vk.SwapchainKHR{swapchain}; const present_image_indices = [_]u32{image_index}; const present_info = vk.PresentInfoKHR{ .wait_semaphore_count = signal_semaphores.len, .p_wait_semaphores = &signal_semaphores, .swapchain_count = present_swapchains.len, .p_swapchains = &present_swapchains, .p_image_indices = &present_image_indices, }; _ = try vkd.queuePresentKHR(graphics_queue, &present_info); std.debug.print("presented one frame\n", .{}); while (!window.shouldClose()) { glfw.pollEvents(); } }