const std = @import("std"); const vk = @import("vulkan"); const c = @import("c.zig"); const resources = @import("resources"); const GraphicsContext = @import("graphics_context.zig").GraphicsContext; const Swapchain = @import("swapchain.zig").Swapchain; const Allocator = std.mem.Allocator; const app_name = "vulkan-zig example"; pub fn main() !void { if (c.glfwInit() != c.GLFW_TRUE) return error.GlfwInitFailed; defer c.glfwTerminate(); 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); const allocator = std.heap.page_allocator; const gc = try GraphicsContext.init(allocator, app_name, window); defer gc.deinit(); std.debug.print("Using device: {}\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); const pipeline = try createPipeline(&gc, extent, pipeline_layout, render_pass); defer gc.vkd.destroyPipeline(gc.dev, pipeline, null); const pool = try gc.vkd.createCommandPool(gc.dev, .{ .flags = .{}, .queue_family_index = gc.graphics_queue.family, }, null); defer gc.vkd.destroyCommandPool(gc.dev, pool, null); var cmdbufs = try createCommandBuffers(&gc, pool, allocator, swapchain); 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, }; if (state == .suboptimal) { var w: c_int = undefined; var h: c_int = undefined; c.glfwGetWindowSize(window, &w, &h); extent.width = @intCast(u32, w); extent.height = @intCast(u32, h); try swapchain.recreate(extent); destroyCommandBuffers(&gc, pool, allocator, cmdbufs); cmdbufs = try createCommandBuffers(&gc, pool, allocator, swapchain); } c.glfwSwapBuffers(window); c.glfwPollEvents(); try gc.vkd.queueWaitIdle(gc.graphics_queue.handle); } } fn createCommandBuffers( gc: *const GraphicsContext, pool: vk.CommandPool, allocator: *Allocator, swapchain: Swapchain, ) ![]vk.CommandBuffer { const cmdbufs = try allocator.alloc(vk.CommandBuffer, swapchain.swap_images.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 subresource_range = vk.ImageSubresourceRange{ .aspect_mask = .{.color_bit = true}, .base_mip_level = 0, .level_count = 1, .base_array_layer = 0, .layer_count = 1, }; const color = vk.ClearColorValue{.float_32 = .{1, 0, 1, 1}}; for (cmdbufs) |cmdbuf, i| { const image = swapchain.swap_images[i].image; try gc.vkd.beginCommandBuffer(cmdbuf, .{ .flags = .{}, .p_inheritance_info = null, }); imageTransition( gc, cmdbuf, image, subresource_range, .{.layout = .@"undefined", .stage = .{.top_of_pipe_bit = true}}, .{.layout = .general, .stage = .{.top_of_pipe_bit = true}}, ); gc.vkd.cmdClearColorImage( cmdbuf, image, .general, color, 1, @ptrCast([*]const vk.ImageSubresourceRange, &subresource_range), ); imageTransition( gc, cmdbuf, image, subresource_range, .{.layout = .general, .stage = .{.top_of_pipe_bit = true}}, .{.layout = .present_src_khr, .stage = .{.bottom_of_pipe_bit = true}}, ); 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 createRenderPass(gc: *const GraphicsContext, swapchain: *const Swapchain) !vk.RenderPass { const color_attachment = vk.AttachmentDescription{ .flags = .{}, .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{ .flags = .{}, .pipeline_bind_point = .graphics, .input_attachment_count = 0, .p_input_attachments = undefined, .color_attachment_count = 1, .p_color_attachments = @ptrCast([*]const vk.AttachmentReference, &color_attachment_ref), .p_resolve_attachments = null, .p_depth_stencil_attachment = null, .preserve_attachment_count = 0, .p_preserve_attachments = undefined, }; return try gc.vkd.createRenderPass(gc.dev, .{ .flags = .{}, .attachment_count = 1, .p_attachments = @ptrCast([*]const vk.AttachmentDescription, &color_attachment), .subpass_count = 1, .p_subpasses = @ptrCast([*]const vk.SubpassDescription, &subpass), .dependency_count = 0, .p_dependencies = undefined, }, null); } fn createPipeline( gc: *const GraphicsContext, extent: vk.Extent2D, layout: vk.PipelineLayout, render_pass: vk.RenderPass, ) !vk.Pipeline { const vert = try gc.vkd.createShaderModule(gc.dev, .{ .flags = .{}, .code_size = resources.triangle_vert.len, .p_code = @ptrCast([*]const u32, resources.triangle_vert), }, null); defer gc.vkd.destroyShaderModule(gc.dev, vert, null); const frag = try gc.vkd.createShaderModule(gc.dev, .{ .flags = .{}, .code_size = resources.triangle_frag.len, .p_code = @ptrCast([*]const u32, resources.triangle_frag), }, null); defer gc.vkd.destroyShaderModule(gc.dev, frag, null); const pssci = [_]vk.PipelineShaderStageCreateInfo{ .{ .flags = .{}, .stage = .{.vertex_bit = true}, .module = vert, .p_name = "main", .p_specialization_info = null, }, .{ .flags = .{}, .stage = .{.fragment_bit = true}, .module = frag, .p_name = "main", .p_specialization_info = null, }, }; const pvisci = vk.PipelineVertexInputStateCreateInfo{ .flags = .{}, .vertex_binding_description_count = 0, .p_vertex_binding_descriptions = undefined, .vertex_attribute_description_count = 0, .p_vertex_attribute_descriptions = undefined, }; const piasci = vk.PipelineInputAssemblyStateCreateInfo{ .flags = .{}, .topology = .triangle_list, .primitive_restart_enable = vk.FALSE, }; const viewport = vk.Viewport{ .x = 0, .y = 0, .width = @intToFloat(f32, extent.width), .height = @intToFloat(f32, extent.height), .min_depth = 0, .max_depth = 1, }; const scissor = vk.Rect2D{ .offset = .{.x = 0, .y = 0}, .extent = extent, }; const pvsci = vk.PipelineViewportStateCreateInfo{ .flags = .{}, .viewport_count = 1, .p_viewports = @ptrCast([*]const vk.Viewport, &viewport), .scissor_count = 1, .p_scissors = @ptrCast([*]const vk.Rect2D, &scissor), }; const prsci = vk.PipelineRasterizationStateCreateInfo{ .flags = .{}, .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{ .flags = .{}, .rasterization_samples = .{.@"1_bit" = true}, .sample_shading_enable = vk.FALSE, .min_sample_shading = 1, .p_sample_mask = null, .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{ .flags = .{}, .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; const pdsci = vk.PipelineDynamicStateCreateInfo{ .flags = .{}, .dynamic_state_count = 1, .p_dynamic_states = @ptrCast([*]const vk.DynamicState, &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; } const ImageState = struct { layout: vk.ImageLayout, stage: vk.PipelineStageFlags, access_mask: vk.AccessFlags = .{}, }; fn imageTransition( gc: *const GraphicsContext, cmdbuf: vk.CommandBuffer, image: vk.Image, subresource_range: vk.ImageSubresourceRange, src: ImageState, dst: ImageState ) void { const barrier = vk.ImageMemoryBarrier{ .src_access_mask = src.access_mask, .dst_access_mask = dst.access_mask, .old_layout = src.layout, .new_layout = dst.layout, .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, .image = image, .subresource_range = subresource_range, }; gc.vkd.cmdPipelineBarrier( cmdbuf, src.stage, dst.stage, .{}, 0, undefined, 0, undefined, 1, @ptrCast([*]const vk.ImageMemoryBarrier, &barrier) ); }