const std = @import("std"); const vk = @import("vulkan"); const c = @import("c.zig"); const shaders = @import("shaders"); const GraphicsContext = @import("graphics_context.zig").GraphicsContext; const Swapchain = @import("swapchain.zig").Swapchain; const Allocator = std.mem.Allocator; const app_name = "vulkan-zig triangle example"; const Vertex = struct { const binding_description = vk.VertexInputBindingDescription{ .binding = 0, .stride = @sizeOf(Vertex), .input_rate = .vertex, }; const attribute_description = [_]vk.VertexInputAttributeDescription{ .{ .binding = 0, .location = 0, .format = .r32g32_sfloat, .offset = @offsetOf(Vertex, "pos"), }, .{ .binding = 0, .location = 1, .format = .r32g32b32_sfloat, .offset = @offsetOf(Vertex, "color"), }, }; pos: [2]f32, color: [3]f32, }; const vertices = [_]Vertex{ .{ .pos = .{ 0, -0.5 }, .color = .{ 1, 0, 0 } }, .{ .pos = .{ 0.5, 0.5 }, .color = .{ 0, 1, 0 } }, .{ .pos = .{ -0.5, 0.5 }, .color = .{ 0, 0, 1 } }, }; pub fn main() !void { if (c.glfwInit() != c.GLFW_TRUE) return error.GlfwInitFailed; defer c.glfwTerminate(); if (c.glfwVulkanSupported() != c.GLFW_TRUE) { std.log.err("GLFW could not find libvulkan", .{}); return error.NoVulkan; } var extent = vk.Extent2D{ .width = 800, .height = 600 }; c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); const window = c.glfwCreateWindow( @intCast(c_int, extent.width), @intCast(c_int, extent.height), app_name, null, null, ) orelse return error.WindowInitFailed; defer c.glfwDestroyWindow(window); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const gc = try GraphicsContext.init(allocator, app_name, window); defer gc.deinit(); std.debug.print("Using device: {s}\n", .{gc.deviceName()}); var swapchain = try Swapchain.init(&gc, allocator, extent); defer swapchain.deinit(); const pipeline_layout = try gc.vkd.createPipelineLayout(gc.dev, &.{ .flags = .{}, .set_layout_count = 0, .p_set_layouts = undefined, .push_constant_range_count = 0, .p_push_constant_ranges = undefined, }, null); defer gc.vkd.destroyPipelineLayout(gc.dev, pipeline_layout, null); const render_pass = try createRenderPass(&gc, swapchain); defer gc.vkd.destroyRenderPass(gc.dev, render_pass, null); var pipeline = try createPipeline(&gc, pipeline_layout, render_pass); defer gc.vkd.destroyPipeline(gc.dev, pipeline, null); var framebuffers = try createFramebuffers(&gc, allocator, render_pass, swapchain); defer destroyFramebuffers(&gc, allocator, framebuffers); const pool = try gc.vkd.createCommandPool(gc.dev, &.{ .queue_family_index = gc.graphics_queue.family, }, null); defer gc.vkd.destroyCommandPool(gc.dev, pool, null); const buffer = try gc.vkd.createBuffer(gc.dev, &.{ .size = @sizeOf(@TypeOf(vertices)), .usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true }, .sharing_mode = .exclusive, }, null); defer gc.vkd.destroyBuffer(gc.dev, buffer, null); const mem_reqs = gc.vkd.getBufferMemoryRequirements(gc.dev, buffer); const memory = try gc.allocate(mem_reqs, .{ .device_local_bit = true }); defer gc.vkd.freeMemory(gc.dev, memory, null); try gc.vkd.bindBufferMemory(gc.dev, buffer, memory, 0); try uploadVertices(&gc, pool, buffer); var cmdbufs = try createCommandBuffers( &gc, pool, allocator, buffer, swapchain.extent, render_pass, pipeline, framebuffers, ); defer destroyCommandBuffers(&gc, pool, allocator, cmdbufs); while (c.glfwWindowShouldClose(window) == c.GLFW_FALSE) { const cmdbuf = cmdbufs[swapchain.image_index]; const state = swapchain.present(cmdbuf) catch |err| switch (err) { error.OutOfDateKHR => Swapchain.PresentState.suboptimal, else => |narrow| return narrow, }; var w: c_int = undefined; var h: c_int = undefined; c.glfwGetWindowSize(window, &w, &h); if (state == .suboptimal or extent.width != @intCast(u32, w) or extent.height != @intCast(u32, h)) { extent.width = @intCast(u32, w); extent.height = @intCast(u32, h); try swapchain.recreate(extent); destroyFramebuffers(&gc, allocator, framebuffers); framebuffers = try createFramebuffers(&gc, allocator, render_pass, swapchain); destroyCommandBuffers(&gc, pool, allocator, cmdbufs); cmdbufs = try createCommandBuffers( &gc, pool, allocator, buffer, swapchain.extent, render_pass, pipeline, framebuffers, ); } c.glfwPollEvents(); } try swapchain.waitForAllFences(); } fn uploadVertices(gc: *const GraphicsContext, pool: vk.CommandPool, buffer: vk.Buffer) !void { const staging_buffer = try gc.vkd.createBuffer(gc.dev, &.{ .size = @sizeOf(@TypeOf(vertices)), .usage = .{ .transfer_src_bit = true }, .sharing_mode = .exclusive, }, null); defer gc.vkd.destroyBuffer(gc.dev, staging_buffer, null); const mem_reqs = gc.vkd.getBufferMemoryRequirements(gc.dev, staging_buffer); const staging_memory = try gc.allocate(mem_reqs, .{ .host_visible_bit = true, .host_coherent_bit = true }); defer gc.vkd.freeMemory(gc.dev, staging_memory, null); try gc.vkd.bindBufferMemory(gc.dev, staging_buffer, staging_memory, 0); { const data = try gc.vkd.mapMemory(gc.dev, staging_memory, 0, vk.WHOLE_SIZE, .{}); defer gc.vkd.unmapMemory(gc.dev, staging_memory); const gpu_vertices = @ptrCast([*]Vertex, @alignCast(@alignOf(Vertex), data)); for (vertices, 0..) |vertex, i| { gpu_vertices[i] = vertex; } } try copyBuffer(gc, pool, buffer, staging_buffer, @sizeOf(@TypeOf(vertices))); } fn copyBuffer(gc: *const GraphicsContext, pool: vk.CommandPool, dst: vk.Buffer, src: vk.Buffer, size: vk.DeviceSize) !void { var cmdbuf: vk.CommandBuffer = undefined; try gc.vkd.allocateCommandBuffers(gc.dev, &.{ .command_pool = pool, .level = .primary, .command_buffer_count = 1, }, @ptrCast([*]vk.CommandBuffer, &cmdbuf)); defer gc.vkd.freeCommandBuffers(gc.dev, pool, 1, @ptrCast([*]const vk.CommandBuffer, &cmdbuf)); try gc.vkd.beginCommandBuffer(cmdbuf, &.{ .flags = .{ .one_time_submit_bit = true }, }); const region = vk.BufferCopy{ .src_offset = 0, .dst_offset = 0, .size = size, }; gc.vkd.cmdCopyBuffer(cmdbuf, src, dst, 1, @ptrCast([*]const vk.BufferCopy, ®ion)); try gc.vkd.endCommandBuffer(cmdbuf); const si = vk.SubmitInfo{ .command_buffer_count = 1, .p_command_buffers = @ptrCast([*]const vk.CommandBuffer, &cmdbuf), .p_wait_dst_stage_mask = undefined, }; try gc.vkd.queueSubmit(gc.graphics_queue.handle, 1, @ptrCast([*]const vk.SubmitInfo, &si), .null_handle); try gc.vkd.queueWaitIdle(gc.graphics_queue.handle); } fn createCommandBuffers( gc: *const GraphicsContext, pool: vk.CommandPool, allocator: Allocator, buffer: vk.Buffer, extent: vk.Extent2D, render_pass: vk.RenderPass, pipeline: vk.Pipeline, framebuffers: []vk.Framebuffer, ) ![]vk.CommandBuffer { const cmdbufs = try allocator.alloc(vk.CommandBuffer, framebuffers.len); errdefer allocator.free(cmdbufs); try gc.vkd.allocateCommandBuffers(gc.dev, &.{ .command_pool = pool, .level = .primary, .command_buffer_count = @truncate(u32, cmdbufs.len), }, cmdbufs.ptr); errdefer gc.vkd.freeCommandBuffers(gc.dev, pool, @truncate(u32, cmdbufs.len), cmdbufs.ptr); const clear = vk.ClearValue{ .color = .{ .float_32 = .{ 0, 0, 0, 1 } }, }; const viewport = vk.Viewport{ .x = 0, .y = 0, .width = @floatFromInt(f32, extent.width), .height = @floatFromInt(f32, extent.height), .min_depth = 0, .max_depth = 1, }; const scissor = vk.Rect2D{ .offset = .{ .x = 0, .y = 0 }, .extent = extent, }; for (cmdbufs, framebuffers) |cmdbuf, framebuffer| { try gc.vkd.beginCommandBuffer(cmdbuf, &.{}); gc.vkd.cmdSetViewport(cmdbuf, 0, 1, @ptrCast([*]const vk.Viewport, &viewport)); gc.vkd.cmdSetScissor(cmdbuf, 0, 1, @ptrCast([*]const vk.Rect2D, &scissor)); // This needs to be a separate definition - see https://github.com/ziglang/zig/issues/7627. const render_area = vk.Rect2D{ .offset = .{ .x = 0, .y = 0 }, .extent = extent, }; gc.vkd.cmdBeginRenderPass(cmdbuf, &.{ .render_pass = render_pass, .framebuffer = framebuffer, .render_area = render_area, .clear_value_count = 1, .p_clear_values = @ptrCast([*]const vk.ClearValue, &clear), }, .@"inline"); gc.vkd.cmdBindPipeline(cmdbuf, .graphics, pipeline); const offset = [_]vk.DeviceSize{0}; gc.vkd.cmdBindVertexBuffers(cmdbuf, 0, 1, @ptrCast([*]const vk.Buffer, &buffer), &offset); gc.vkd.cmdDraw(cmdbuf, vertices.len, 1, 0, 0); gc.vkd.cmdEndRenderPass(cmdbuf); try gc.vkd.endCommandBuffer(cmdbuf); } return cmdbufs; } fn destroyCommandBuffers(gc: *const GraphicsContext, pool: vk.CommandPool, allocator: Allocator, cmdbufs: []vk.CommandBuffer) void { gc.vkd.freeCommandBuffers(gc.dev, pool, @truncate(u32, cmdbufs.len), cmdbufs.ptr); allocator.free(cmdbufs); } fn createFramebuffers(gc: *const GraphicsContext, allocator: Allocator, render_pass: vk.RenderPass, swapchain: Swapchain) ![]vk.Framebuffer { const framebuffers = try allocator.alloc(vk.Framebuffer, swapchain.swap_images.len); errdefer allocator.free(framebuffers); var i: usize = 0; errdefer for (framebuffers[0..i]) |fb| gc.vkd.destroyFramebuffer(gc.dev, fb, null); for (framebuffers) |*fb| { fb.* = try gc.vkd.createFramebuffer(gc.dev, &.{ .render_pass = render_pass, .attachment_count = 1, .p_attachments = @ptrCast([*]const vk.ImageView, &swapchain.swap_images[i].view), .width = swapchain.extent.width, .height = swapchain.extent.height, .layers = 1, }, null); i += 1; } return framebuffers; } fn destroyFramebuffers(gc: *const GraphicsContext, allocator: Allocator, framebuffers: []const vk.Framebuffer) void { for (framebuffers) |fb| gc.vkd.destroyFramebuffer(gc.dev, fb, null); allocator.free(framebuffers); } fn createRenderPass(gc: *const GraphicsContext, swapchain: Swapchain) !vk.RenderPass { const color_attachment = vk.AttachmentDescription{ .format = swapchain.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([*]const vk.AttachmentReference, &color_attachment_ref), }; return try gc.vkd.createRenderPass(gc.dev, &.{ .attachment_count = 1, .p_attachments = @ptrCast([*]const vk.AttachmentDescription, &color_attachment), .subpass_count = 1, .p_subpasses = @ptrCast([*]const vk.SubpassDescription, &subpass), }, null); } fn createPipeline( gc: *const GraphicsContext, layout: vk.PipelineLayout, render_pass: vk.RenderPass, ) !vk.Pipeline { const vert = try gc.vkd.createShaderModule(gc.dev, &.{ .code_size = shaders.triangle_vert.len, .p_code = @ptrCast([*]const u32, &shaders.triangle_vert), }, null); defer gc.vkd.destroyShaderModule(gc.dev, vert, null); const frag = try gc.vkd.createShaderModule(gc.dev, &.{ .code_size = shaders.triangle_frag.len, .p_code = @ptrCast([*]const u32, &shaders.triangle_frag), }, null); defer gc.vkd.destroyShaderModule(gc.dev, frag, null); const pssci = [_]vk.PipelineShaderStageCreateInfo{ .{ .stage = .{ .vertex_bit = true }, .module = vert, .p_name = "main", }, .{ .stage = .{ .fragment_bit = true }, .module = frag, .p_name = "main", }, }; const pvisci = vk.PipelineVertexInputStateCreateInfo{ .vertex_binding_description_count = 1, .p_vertex_binding_descriptions = @ptrCast([*]const vk.VertexInputBindingDescription, &Vertex.binding_description), .vertex_attribute_description_count = Vertex.attribute_description.len, .p_vertex_attribute_descriptions = &Vertex.attribute_description, }; const piasci = vk.PipelineInputAssemblyStateCreateInfo{ .topology = .triangle_list, .primitive_restart_enable = vk.FALSE, }; const pvsci = vk.PipelineViewportStateCreateInfo{ .viewport_count = 1, .p_viewports = undefined, // set in createCommandBuffers with cmdSetViewport .scissor_count = 1, .p_scissors = undefined, // set in createCommandBuffers with cmdSetScissor }; const prsci = vk.PipelineRasterizationStateCreateInfo{ .depth_clamp_enable = vk.FALSE, .rasterizer_discard_enable = vk.FALSE, .polygon_mode = .fill, .cull_mode = .{ .back_bit = true }, .front_face = .clockwise, .depth_bias_enable = vk.FALSE, .depth_bias_constant_factor = 0, .depth_bias_clamp = 0, .depth_bias_slope_factor = 0, .line_width = 1, }; const pmsci = vk.PipelineMultisampleStateCreateInfo{ .rasterization_samples = .{ .@"1_bit" = true }, .sample_shading_enable = vk.FALSE, .min_sample_shading = 1, .alpha_to_coverage_enable = vk.FALSE, .alpha_to_one_enable = vk.FALSE, }; const pcbas = vk.PipelineColorBlendAttachmentState{ .blend_enable = vk.FALSE, .src_color_blend_factor = .one, .dst_color_blend_factor = .zero, .color_blend_op = .add, .src_alpha_blend_factor = .one, .dst_alpha_blend_factor = .zero, .alpha_blend_op = .add, .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true }, }; const pcbsci = vk.PipelineColorBlendStateCreateInfo{ .logic_op_enable = vk.FALSE, .logic_op = .copy, .attachment_count = 1, .p_attachments = @ptrCast([*]const vk.PipelineColorBlendAttachmentState, &pcbas), .blend_constants = [_]f32{ 0, 0, 0, 0 }, }; const dynstate = [_]vk.DynamicState{ .viewport, .scissor }; const pdsci = vk.PipelineDynamicStateCreateInfo{ .flags = .{}, .dynamic_state_count = dynstate.len, .p_dynamic_states = &dynstate, }; const gpci = vk.GraphicsPipelineCreateInfo{ .flags = .{}, .stage_count = 2, .p_stages = &pssci, .p_vertex_input_state = &pvisci, .p_input_assembly_state = &piasci, .p_tessellation_state = null, .p_viewport_state = &pvsci, .p_rasterization_state = &prsci, .p_multisample_state = &pmsci, .p_depth_stencil_state = null, .p_color_blend_state = &pcbsci, .p_dynamic_state = &pdsci, .layout = layout, .render_pass = render_pass, .subpass = 0, .base_pipeline_handle = .null_handle, .base_pipeline_index = -1, }; var pipeline: vk.Pipeline = undefined; _ = try gc.vkd.createGraphicsPipelines( gc.dev, .null_handle, 1, @ptrCast([*]const vk.GraphicsPipelineCreateInfo, &gpci), null, @ptrCast([*]vk.Pipeline, &pipeline), ); return pipeline; }