├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon ├── shaders ├── triangle.frag └── triangle.vert └── src ├── GraphicsContext.zig ├── Swapchain.zig ├── main.zig └── xcb.zig /.gitignore: -------------------------------------------------------------------------------- 1 | /.zig-cache/ 2 | /zig-out/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (Expat) 2 | 3 | Copyright (c) 2021 Andrew Kelley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-vulkan-triangle 2 | 3 | Example of using [vulkan-zig](https://github.com/Snektron/vulkan-zig) and 4 | [shader_compiler](https://github.com/Games-by-Mason/shader_compiler) along with 5 | libxcb to open a window and draw a triangle. 6 | 7 | ![](https://i.imgur.com/pHEHvMU.png) 8 | 9 | ## Building and Running 10 | 11 | ``` 12 | zig build run 13 | ``` 14 | 15 | ## System Configuration 16 | 17 | On NixOS, I had to add these to my shell: 18 | 19 | ``` 20 | buildInputs = [ 21 | vulkan-loader 22 | vulkan-validation-layers 23 | xorg.libxcb 24 | ]; 25 | 26 | VK_LAYER_PATH="${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"; 27 | ``` 28 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml"); 8 | const shader_compiler = b.dependency("shader_compiler", .{ 9 | .target = b.host, 10 | .optimize = .ReleaseFast, 11 | }).artifact("shader_compiler"); 12 | 13 | const exe = b.addExecutable(.{ 14 | .name = "vulkan-triangle-example", 15 | .root_source_file = b.path("src/main.zig"), 16 | .target = target, 17 | .optimize = optimize, 18 | .link_libc = true, 19 | }); 20 | exe.linkSystemLibrary("vulkan"); 21 | exe.linkSystemLibrary("xcb"); 22 | b.installArtifact(exe); 23 | 24 | const vk_gen = b.dependency("vulkan", .{}).artifact("vulkan-zig-generator"); 25 | const vk_generate_cmd = b.addRunArtifact(vk_gen); 26 | vk_generate_cmd.addFileArg(registry); 27 | 28 | exe.root_module.addAnonymousImport("vulkan", .{ 29 | .root_source_file = vk_generate_cmd.addOutputFileArg("vk.zig"), 30 | }); 31 | 32 | exe.root_module.addAnonymousImport("shaders.triangle.vert", .{ 33 | .root_source_file = compileShader(b, optimize, shader_compiler, b.path("shaders/triangle.vert"), "triangle.vert.spv"), 34 | }); 35 | exe.root_module.addAnonymousImport("shaders.triangle.frag", .{ 36 | .root_source_file = compileShader(b, optimize, shader_compiler, b.path("shaders/triangle.frag"), "triangle.frag.spv"), 37 | }); 38 | 39 | const run_cmd = b.addRunArtifact(exe); 40 | run_cmd.step.dependOn(b.getInstallStep()); 41 | if (b.args) |args| { 42 | run_cmd.addArgs(args); 43 | } 44 | 45 | const run_step = b.step("run", "Run the app"); 46 | run_step.dependOn(&run_cmd.step); 47 | } 48 | 49 | fn compileShader( 50 | b: *std.Build, 51 | optimize: std.builtin.OptimizeMode, 52 | shader_compiler: *std.Build.Step.Compile, 53 | src: std.Build.LazyPath, 54 | out_basename: []const u8, 55 | ) std.Build.LazyPath { 56 | const compile_shader = b.addRunArtifact(shader_compiler); 57 | compile_shader.addArgs(&.{ 58 | "--target", "Vulkan-1.3", 59 | }); 60 | switch (optimize) { 61 | .Debug => compile_shader.addArgs(&.{ 62 | "--robust-access", 63 | }), 64 | .ReleaseSafe => compile_shader.addArgs(&.{ 65 | "--optimize-perf", 66 | "--robust-access", 67 | }), 68 | .ReleaseFast => compile_shader.addArgs(&.{ 69 | "--optimize-perf", 70 | }), 71 | .ReleaseSmall => compile_shader.addArgs(&.{ 72 | "--optimize-perf", 73 | "--optimize-small", 74 | }), 75 | } 76 | compile_shader.addFileArg(src); 77 | return compile_shader.addOutputFileArg(out_basename); 78 | } 79 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "vulkan-triangle-example", 3 | .version = "0.0.0", 4 | .minimum_zig_version = "0.14.0-dev.1359+e9a00ba7f", 5 | 6 | .dependencies = .{ 7 | .vulkan = .{ 8 | .url = "git+https://github.com/andrewrk/vulkan-zig.git#b6e589d62bc1d9070c47d73ce942168fb92624c8", 9 | .hash = "12201e484e173e70634e664864763223427703e677f28c63ebec9332513c8ca5121c", 10 | }, 11 | .vulkan_headers = .{ 12 | .url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz", 13 | .hash = "1220a7e73d72a0d56bc2a65f9d8999a7c019e42260a0744c408d1cded111bc205e10", 14 | }, 15 | .shader_compiler = .{ 16 | .url = "git+https://github.com/andrewrk/shader_compiler.git#89550bb2cbe85276bd1fd89d020f3bbd686937c3", 17 | .hash = "12207b775b98ac87f583d5ca95386537432c22f4bc83bc0c7fd9d6cbfdf265cc0444", 18 | }, 19 | }, 20 | 21 | .paths = .{ 22 | "build.zig", 23 | "build.zig.zon", 24 | "src", 25 | "LICENSE", 26 | "README.md", 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /shaders/triangle.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 v_color; 4 | 5 | layout(location = 0) out vec4 f_color; 6 | 7 | void main() { 8 | f_color = vec4(v_color, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /shaders/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 a_pos; 4 | layout(location = 1) in vec3 a_color; 5 | 6 | layout(location = 0) out vec3 v_color; 7 | 8 | void main() { 9 | gl_Position = vec4(a_pos, 0.0, 1.0); 10 | v_color = a_color; 11 | } 12 | -------------------------------------------------------------------------------- /src/GraphicsContext.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const vk = @import("vulkan"); 3 | const Allocator = std.mem.Allocator; 4 | const GraphicsContext = @This(); 5 | 6 | const required_device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name}; 7 | 8 | /// To construct base, instance and device wrappers for vulkan-zig, you need to pass a list of 'apis' to it. 9 | const apis: []const vk.ApiInfo = &.{ 10 | // You can either add invidiual functions by manually creating an 'api' 11 | .{ 12 | .base_commands = .{ 13 | .createInstance = true, 14 | .enumerateInstanceLayerProperties = true, 15 | }, 16 | .instance_commands = .{ 17 | .createDevice = true, 18 | .createXcbSurfaceKHR = true, 19 | }, 20 | }, 21 | // Or you can add entire feature sets or extensions 22 | vk.features.version_1_0, 23 | vk.extensions.khr_surface, 24 | vk.extensions.khr_swapchain, 25 | vk.extensions.ext_debug_utils, 26 | }; 27 | 28 | /// Next, pass the `apis` to the wrappers to create dispatch tables. 29 | const BaseDispatch = vk.BaseWrapper(apis); 30 | const InstanceDispatch = vk.InstanceWrapper(apis); 31 | const DeviceDispatch = vk.DeviceWrapper(apis); 32 | 33 | // Also create some proxying wrappers, which also have the respective handles 34 | const Instance = vk.InstanceProxy(apis); 35 | const Device = vk.DeviceProxy(apis); 36 | 37 | pub const CommandBuffer = vk.CommandBufferProxy(apis); 38 | 39 | allocator: Allocator, 40 | 41 | vkb: BaseDispatch, 42 | 43 | instance: Instance, 44 | surface: vk.SurfaceKHR, 45 | pdev: vk.PhysicalDevice, 46 | props: vk.PhysicalDeviceProperties, 47 | mem_props: vk.PhysicalDeviceMemoryProperties, 48 | 49 | dev: Device, 50 | graphics_queue: Queue, 51 | present_queue: Queue, 52 | 53 | debug_messenger: vk.DebugUtilsMessengerEXT, 54 | 55 | const vkGetInstanceProcAddr = @extern(vk.PfnGetInstanceProcAddr, .{ 56 | .name = "vkGetInstanceProcAddr", 57 | .library_name = "vulkan", 58 | }); 59 | 60 | pub fn init( 61 | allocator: Allocator, 62 | app_name: [*:0]const u8, 63 | connection: *vk.xcb_connection_t, 64 | window: vk.xcb_window_t, 65 | enable_validation_layers: bool, 66 | ) !GraphicsContext { 67 | var self: GraphicsContext = undefined; 68 | self.allocator = allocator; 69 | self.vkb = try BaseDispatch.load(vkGetInstanceProcAddr); 70 | 71 | const app_info: vk.ApplicationInfo = .{ 72 | .p_application_name = app_name, 73 | .application_version = vk.makeApiVersion(0, 0, 0, 0), 74 | .p_engine_name = app_name, 75 | .engine_version = vk.makeApiVersion(0, 0, 0, 0), 76 | .api_version = vk.API_VERSION_1_2, 77 | }; 78 | 79 | var extension_names_buffer: [3][*:0]const u8 = undefined; 80 | var extension_names: std.ArrayListUnmanaged([*:0]const u8) = .{ 81 | .items = extension_names_buffer[0..0], 82 | .capacity = extension_names_buffer.len, 83 | }; 84 | extension_names.appendAssumeCapacity("VK_KHR_surface"); 85 | extension_names.appendAssumeCapacity("VK_KHR_xcb_surface"); 86 | if (enable_validation_layers) 87 | extension_names.appendAssumeCapacity("VK_EXT_debug_utils"); 88 | 89 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 90 | const enabled_layers: []const [*:0]const u8 = if (enable_validation_layers) &validation_layers else &.{}; 91 | 92 | const instance = try self.vkb.createInstance(&.{ 93 | .p_application_info = &app_info, 94 | 95 | .enabled_extension_count = @intCast(extension_names.items.len), 96 | .pp_enabled_extension_names = extension_names.items.ptr, 97 | 98 | .enabled_layer_count = @intCast(enabled_layers.len), 99 | .pp_enabled_layer_names = enabled_layers.ptr, 100 | }, null); 101 | 102 | const vki = try allocator.create(InstanceDispatch); 103 | errdefer allocator.destroy(vki); 104 | vki.* = try InstanceDispatch.load(instance, self.vkb.dispatch.vkGetInstanceProcAddr); 105 | self.instance = Instance.init(instance, vki); 106 | errdefer self.instance.destroyInstance(null); 107 | 108 | if (enable_validation_layers) { 109 | self.debug_messenger = try self.instance.createDebugUtilsMessengerEXT(&.{ 110 | .message_severity = .{ 111 | .error_bit_ext = true, 112 | .warning_bit_ext = true, 113 | }, 114 | .message_type = .{ 115 | .general_bit_ext = true, 116 | .validation_bit_ext = true, 117 | .performance_bit_ext = true, 118 | .device_address_binding_bit_ext = true, 119 | }, 120 | .pfn_user_callback = debugCallback, 121 | }, null); 122 | } 123 | 124 | self.surface = try createSurface(self.instance, connection, window); 125 | errdefer self.instance.destroySurfaceKHR(self.surface, null); 126 | 127 | const candidate = try pickPhysicalDevice(self.instance, allocator, self.surface); 128 | self.pdev = candidate.pdev; 129 | self.props = candidate.props; 130 | 131 | const dev = try initializeCandidate(self.instance, candidate); 132 | 133 | const vkd = try allocator.create(DeviceDispatch); 134 | errdefer allocator.destroy(vkd); 135 | vkd.* = try DeviceDispatch.load(dev, self.instance.wrapper.dispatch.vkGetDeviceProcAddr); 136 | self.dev = Device.init(dev, vkd); 137 | errdefer self.dev.destroyDevice(null); 138 | 139 | self.graphics_queue = Queue.init(self.dev, candidate.queues.graphics_family); 140 | self.present_queue = Queue.init(self.dev, candidate.queues.present_family); 141 | 142 | self.mem_props = self.instance.getPhysicalDeviceMemoryProperties(self.pdev); 143 | 144 | return self; 145 | } 146 | 147 | pub fn deinit(self: GraphicsContext) void { 148 | self.instance.destroyDebugUtilsMessengerEXT(self.debug_messenger, null); 149 | self.dev.destroyDevice(null); 150 | self.instance.destroySurfaceKHR(self.surface, null); 151 | self.instance.destroyInstance(null); 152 | 153 | // Don't forget to free the tables to prevent a memory leak. 154 | self.allocator.destroy(self.dev.wrapper); 155 | self.allocator.destroy(self.instance.wrapper); 156 | } 157 | 158 | pub fn deviceName(self: *const GraphicsContext) []const u8 { 159 | return std.mem.sliceTo(&self.props.device_name, 0); 160 | } 161 | 162 | pub fn findMemoryTypeIndex(self: GraphicsContext, memory_type_bits: u32, flags: vk.MemoryPropertyFlags) !u32 { 163 | for (self.mem_props.memory_types[0..self.mem_props.memory_type_count], 0..) |mem_type, i| { 164 | if (memory_type_bits & (@as(u32, 1) << @truncate(i)) != 0 and mem_type.property_flags.contains(flags)) { 165 | return @truncate(i); 166 | } 167 | } 168 | 169 | return error.NoSuitableMemoryType; 170 | } 171 | 172 | pub fn allocate(self: GraphicsContext, requirements: vk.MemoryRequirements, flags: vk.MemoryPropertyFlags) !vk.DeviceMemory { 173 | return try self.dev.allocateMemory(&.{ 174 | .allocation_size = requirements.size, 175 | .memory_type_index = try self.findMemoryTypeIndex(requirements.memory_type_bits, flags), 176 | }, null); 177 | } 178 | 179 | pub const Queue = struct { 180 | handle: vk.Queue, 181 | family: u32, 182 | 183 | fn init(device: Device, family: u32) Queue { 184 | return .{ 185 | .handle = device.getDeviceQueue(family, 0), 186 | .family = family, 187 | }; 188 | } 189 | }; 190 | 191 | fn createSurface(instance: Instance, connection: *vk.xcb_connection_t, window: vk.xcb_window_t) !vk.SurfaceKHR { 192 | var surface_create_info: vk.XcbSurfaceCreateInfoKHR = .{ 193 | .connection = connection, 194 | .window = window, 195 | }; 196 | return instance.createXcbSurfaceKHR(&surface_create_info, null); 197 | } 198 | 199 | fn initializeCandidate(instance: Instance, candidate: DeviceCandidate) !vk.Device { 200 | const priority = [_]f32{1}; 201 | const qci = [_]vk.DeviceQueueCreateInfo{ 202 | .{ 203 | .queue_family_index = candidate.queues.graphics_family, 204 | .queue_count = 1, 205 | .p_queue_priorities = &priority, 206 | }, 207 | .{ 208 | .queue_family_index = candidate.queues.present_family, 209 | .queue_count = 1, 210 | .p_queue_priorities = &priority, 211 | }, 212 | }; 213 | 214 | const queue_count: u32 = if (candidate.queues.graphics_family == candidate.queues.present_family) 215 | 1 216 | else 217 | 2; 218 | 219 | return try instance.createDevice(candidate.pdev, &.{ 220 | .queue_create_info_count = queue_count, 221 | .p_queue_create_infos = &qci, 222 | .enabled_extension_count = required_device_extensions.len, 223 | .pp_enabled_extension_names = @ptrCast(&required_device_extensions), 224 | }, null); 225 | } 226 | 227 | const DeviceCandidate = struct { 228 | pdev: vk.PhysicalDevice, 229 | props: vk.PhysicalDeviceProperties, 230 | queues: QueueAllocation, 231 | }; 232 | 233 | const QueueAllocation = struct { 234 | graphics_family: u32, 235 | present_family: u32, 236 | }; 237 | 238 | fn pickPhysicalDevice( 239 | instance: Instance, 240 | allocator: Allocator, 241 | surface: vk.SurfaceKHR, 242 | ) !DeviceCandidate { 243 | const pdevs = try instance.enumeratePhysicalDevicesAlloc(allocator); 244 | defer allocator.free(pdevs); 245 | 246 | for (pdevs) |pdev| { 247 | if (try checkSuitable(instance, pdev, allocator, surface)) |candidate| { 248 | return candidate; 249 | } 250 | } 251 | 252 | return error.NoSuitableDevice; 253 | } 254 | 255 | fn checkSuitable( 256 | instance: Instance, 257 | pdev: vk.PhysicalDevice, 258 | allocator: Allocator, 259 | surface: vk.SurfaceKHR, 260 | ) !?DeviceCandidate { 261 | if (!try checkExtensionSupport(instance, pdev, allocator)) { 262 | return null; 263 | } 264 | 265 | if (!try checkSurfaceSupport(instance, pdev, surface)) { 266 | return null; 267 | } 268 | 269 | if (try allocateQueues(instance, pdev, allocator, surface)) |allocation| { 270 | const props = instance.getPhysicalDeviceProperties(pdev); 271 | return DeviceCandidate{ 272 | .pdev = pdev, 273 | .props = props, 274 | .queues = allocation, 275 | }; 276 | } 277 | 278 | return null; 279 | } 280 | 281 | fn allocateQueues(instance: Instance, pdev: vk.PhysicalDevice, allocator: Allocator, surface: vk.SurfaceKHR) !?QueueAllocation { 282 | const families = try instance.getPhysicalDeviceQueueFamilyPropertiesAlloc(pdev, allocator); 283 | defer allocator.free(families); 284 | 285 | var graphics_family: ?u32 = null; 286 | var present_family: ?u32 = null; 287 | 288 | for (families, 0..) |properties, i| { 289 | const family: u32 = @intCast(i); 290 | 291 | if (graphics_family == null and properties.queue_flags.graphics_bit) { 292 | graphics_family = family; 293 | } 294 | 295 | if (present_family == null and (try instance.getPhysicalDeviceSurfaceSupportKHR(pdev, family, surface)) == vk.TRUE) { 296 | present_family = family; 297 | } 298 | } 299 | 300 | if (graphics_family != null and present_family != null) { 301 | return QueueAllocation{ 302 | .graphics_family = graphics_family.?, 303 | .present_family = present_family.?, 304 | }; 305 | } 306 | 307 | return null; 308 | } 309 | 310 | fn checkSurfaceSupport(instance: Instance, pdev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !bool { 311 | var format_count: u32 = undefined; 312 | _ = try instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, null); 313 | 314 | var present_mode_count: u32 = undefined; 315 | _ = try instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, null); 316 | 317 | return format_count > 0 and present_mode_count > 0; 318 | } 319 | 320 | fn checkExtensionSupport( 321 | instance: Instance, 322 | pdev: vk.PhysicalDevice, 323 | allocator: Allocator, 324 | ) !bool { 325 | const propsv = try instance.enumerateDeviceExtensionPropertiesAlloc(pdev, null, allocator); 326 | defer allocator.free(propsv); 327 | 328 | for (required_device_extensions) |ext| { 329 | for (propsv) |props| { 330 | if (std.mem.eql(u8, std.mem.span(ext), std.mem.sliceTo(&props.extension_name, 0))) { 331 | break; 332 | } 333 | } else { 334 | return false; 335 | } 336 | } 337 | 338 | return true; 339 | } 340 | 341 | fn debugCallback( 342 | message_severity: vk.DebugUtilsMessageSeverityFlagsEXT, 343 | message_types: vk.DebugUtilsMessageTypeFlagsEXT, 344 | p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, 345 | p_user_data: ?*anyopaque, 346 | ) callconv(vk.vulkan_call_conv) vk.Bool32 { 347 | _ = message_severity; 348 | _ = message_types; 349 | _ = p_user_data; 350 | b: { 351 | const msg = (p_callback_data orelse break :b).p_message orelse break :b; 352 | std.log.scoped(.validation).warn("{s}", .{msg}); 353 | return vk.FALSE; 354 | } 355 | std.log.scoped(.validation).warn("unrecognized validation layer debug message", .{}); 356 | return vk.FALSE; 357 | } 358 | -------------------------------------------------------------------------------- /src/Swapchain.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const vk = @import("vulkan"); 3 | const GraphicsContext = @import("GraphicsContext.zig"); 4 | const Allocator = std.mem.Allocator; 5 | const Swapchain = @This(); 6 | 7 | pub const PresentState = enum { 8 | optimal, 9 | suboptimal, 10 | }; 11 | 12 | gc: *const GraphicsContext, 13 | allocator: Allocator, 14 | 15 | surface_format: vk.SurfaceFormatKHR, 16 | present_mode: vk.PresentModeKHR, 17 | extent: vk.Extent2D, 18 | handle: vk.SwapchainKHR, 19 | 20 | swap_images: []SwapImage, 21 | image_index: u32, 22 | next_image_acquired: vk.Semaphore, 23 | 24 | pub fn init(gc: *const GraphicsContext, allocator: Allocator, extent: vk.Extent2D) !Swapchain { 25 | return try initRecycle(gc, allocator, extent, .null_handle); 26 | } 27 | 28 | pub fn initRecycle(gc: *const GraphicsContext, allocator: Allocator, extent: vk.Extent2D, old_handle: vk.SwapchainKHR) !Swapchain { 29 | const caps = try gc.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(gc.pdev, gc.surface); 30 | const actual_extent = findActualExtent(caps, extent); 31 | if (actual_extent.width == 0 or actual_extent.height == 0) { 32 | return error.InvalidSurfaceDimensions; 33 | } 34 | 35 | const surface_format = try findSurfaceFormat(gc, allocator); 36 | const present_mode = try findPresentMode(gc, allocator); 37 | 38 | var image_count = caps.min_image_count + 1; 39 | if (caps.max_image_count > 0) { 40 | image_count = @min(image_count, caps.max_image_count); 41 | } 42 | 43 | const qfi = [_]u32{ gc.graphics_queue.family, gc.present_queue.family }; 44 | const sharing_mode: vk.SharingMode = if (gc.graphics_queue.family != gc.present_queue.family) 45 | .concurrent 46 | else 47 | .exclusive; 48 | 49 | const handle = try gc.dev.createSwapchainKHR(&.{ 50 | .surface = gc.surface, 51 | .min_image_count = image_count, 52 | .image_format = surface_format.format, 53 | .image_color_space = surface_format.color_space, 54 | .image_extent = actual_extent, 55 | .image_array_layers = 1, 56 | .image_usage = .{ .color_attachment_bit = true, .transfer_dst_bit = true }, 57 | .image_sharing_mode = sharing_mode, 58 | .queue_family_index_count = qfi.len, 59 | .p_queue_family_indices = &qfi, 60 | .pre_transform = caps.current_transform, 61 | .composite_alpha = .{ .opaque_bit_khr = true }, 62 | .present_mode = present_mode, 63 | .clipped = vk.TRUE, 64 | .old_swapchain = old_handle, 65 | }, null); 66 | errdefer gc.dev.destroySwapchainKHR(handle, null); 67 | 68 | if (old_handle != .null_handle) { 69 | // Apparently, the old swapchain handle still needs to be destroyed after recreating. 70 | gc.dev.destroySwapchainKHR(old_handle, null); 71 | } 72 | 73 | const swap_images = try initSwapchainImages(gc, handle, surface_format.format, allocator); 74 | errdefer { 75 | for (swap_images) |si| si.deinit(gc); 76 | allocator.free(swap_images); 77 | } 78 | 79 | var next_image_acquired = try gc.dev.createSemaphore(&.{}, null); 80 | errdefer gc.dev.destroySemaphore(next_image_acquired, null); 81 | 82 | const result = try gc.dev.acquireNextImageKHR(handle, std.math.maxInt(u64), next_image_acquired, .null_handle); 83 | if (result.result != .success) { 84 | return error.ImageAcquireFailed; 85 | } 86 | 87 | std.mem.swap(vk.Semaphore, &swap_images[result.image_index].image_acquired, &next_image_acquired); 88 | return Swapchain{ 89 | .gc = gc, 90 | .allocator = allocator, 91 | .surface_format = surface_format, 92 | .present_mode = present_mode, 93 | .extent = actual_extent, 94 | .handle = handle, 95 | .swap_images = swap_images, 96 | .image_index = result.image_index, 97 | .next_image_acquired = next_image_acquired, 98 | }; 99 | } 100 | 101 | fn deinitExceptSwapchain(self: Swapchain) void { 102 | for (self.swap_images) |si| si.deinit(self.gc); 103 | self.allocator.free(self.swap_images); 104 | self.gc.dev.destroySemaphore(self.next_image_acquired, null); 105 | } 106 | 107 | pub fn waitForAllFences(self: Swapchain) !void { 108 | for (self.swap_images) |si| si.waitForFence(self.gc) catch {}; 109 | } 110 | 111 | pub fn deinit(self: Swapchain) void { 112 | self.deinitExceptSwapchain(); 113 | self.gc.dev.destroySwapchainKHR(self.handle, null); 114 | } 115 | 116 | pub fn recreate(self: *Swapchain, new_extent: vk.Extent2D) !void { 117 | const gc = self.gc; 118 | const allocator = self.allocator; 119 | const old_handle = self.handle; 120 | self.deinitExceptSwapchain(); 121 | self.* = try initRecycle(gc, allocator, new_extent, old_handle); 122 | } 123 | 124 | pub fn currentImage(self: Swapchain) vk.Image { 125 | return self.swap_images[self.image_index].image; 126 | } 127 | 128 | pub fn currentSwapImage(self: Swapchain) *const SwapImage { 129 | return &self.swap_images[self.image_index]; 130 | } 131 | 132 | pub fn present(self: *Swapchain, cmdbuf: vk.CommandBuffer) !PresentState { 133 | // Simple method: 134 | // 1) Acquire next image 135 | // 2) Wait for and reset fence of the acquired image 136 | // 3) Submit command buffer with fence of acquired image, 137 | // dependendent on the semaphore signalled by the first step. 138 | // 4) Present current frame, dependent on semaphore signalled by previous step 139 | // Problem: This way we can't reference the current image while rendering. 140 | // Better method: Shuffle the steps around such that acquire next image is the last step, 141 | // leaving the swapchain in a state with the current image. 142 | // 1) Wait for and reset fence of current image 143 | // 2) Submit command buffer, signalling fence of current image and dependent on 144 | // the semaphore signalled by step 4. 145 | // 3) Present current frame, dependent on semaphore signalled by the submit 146 | // 4) Acquire next image, signalling its semaphore 147 | // One problem that arises is that we can't know beforehand which semaphore to signal, 148 | // so we keep an extra auxilery semaphore that is swapped around 149 | 150 | // Step 1: Make sure the current frame has finished rendering 151 | const current = self.currentSwapImage(); 152 | try current.waitForFence(self.gc); 153 | try self.gc.dev.resetFences(1, @ptrCast(¤t.frame_fence)); 154 | 155 | // Step 2: Submit the command buffer 156 | const wait_stage = [_]vk.PipelineStageFlags{.{ .top_of_pipe_bit = true }}; 157 | try self.gc.dev.queueSubmit(self.gc.graphics_queue.handle, 1, &[_]vk.SubmitInfo{.{ 158 | .wait_semaphore_count = 1, 159 | .p_wait_semaphores = @ptrCast(¤t.image_acquired), 160 | .p_wait_dst_stage_mask = &wait_stage, 161 | .command_buffer_count = 1, 162 | .p_command_buffers = @ptrCast(&cmdbuf), 163 | .signal_semaphore_count = 1, 164 | .p_signal_semaphores = @ptrCast(¤t.render_finished), 165 | }}, current.frame_fence); 166 | 167 | // Step 3: Present the current frame 168 | _ = try self.gc.dev.queuePresentKHR(self.gc.present_queue.handle, &.{ 169 | .wait_semaphore_count = 1, 170 | .p_wait_semaphores = @ptrCast(¤t.render_finished), 171 | .swapchain_count = 1, 172 | .p_swapchains = @ptrCast(&self.handle), 173 | .p_image_indices = @ptrCast(&self.image_index), 174 | }); 175 | 176 | // Step 4: Acquire next frame 177 | const result = try self.gc.dev.acquireNextImageKHR( 178 | self.handle, 179 | std.math.maxInt(u64), 180 | self.next_image_acquired, 181 | .null_handle, 182 | ); 183 | 184 | std.mem.swap(vk.Semaphore, &self.swap_images[result.image_index].image_acquired, &self.next_image_acquired); 185 | self.image_index = result.image_index; 186 | 187 | return switch (result.result) { 188 | .success => .optimal, 189 | .suboptimal_khr => .suboptimal, 190 | else => unreachable, 191 | }; 192 | } 193 | 194 | const SwapImage = struct { 195 | image: vk.Image, 196 | view: vk.ImageView, 197 | image_acquired: vk.Semaphore, 198 | render_finished: vk.Semaphore, 199 | frame_fence: vk.Fence, 200 | 201 | fn init(gc: *const GraphicsContext, image: vk.Image, format: vk.Format) !SwapImage { 202 | const view = try gc.dev.createImageView(&.{ 203 | .image = image, 204 | .view_type = .@"2d", 205 | .format = format, 206 | .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, 207 | .subresource_range = .{ 208 | .aspect_mask = .{ .color_bit = true }, 209 | .base_mip_level = 0, 210 | .level_count = 1, 211 | .base_array_layer = 0, 212 | .layer_count = 1, 213 | }, 214 | }, null); 215 | errdefer gc.dev.destroyImageView(view, null); 216 | 217 | const image_acquired = try gc.dev.createSemaphore(&.{}, null); 218 | errdefer gc.dev.destroySemaphore(image_acquired, null); 219 | 220 | const render_finished = try gc.dev.createSemaphore(&.{}, null); 221 | errdefer gc.dev.destroySemaphore(render_finished, null); 222 | 223 | const frame_fence = try gc.dev.createFence(&.{ .flags = .{ .signaled_bit = true } }, null); 224 | errdefer gc.dev.destroyFence(frame_fence, null); 225 | 226 | return SwapImage{ 227 | .image = image, 228 | .view = view, 229 | .image_acquired = image_acquired, 230 | .render_finished = render_finished, 231 | .frame_fence = frame_fence, 232 | }; 233 | } 234 | 235 | fn deinit(self: SwapImage, gc: *const GraphicsContext) void { 236 | self.waitForFence(gc) catch return; 237 | gc.dev.destroyImageView(self.view, null); 238 | gc.dev.destroySemaphore(self.image_acquired, null); 239 | gc.dev.destroySemaphore(self.render_finished, null); 240 | gc.dev.destroyFence(self.frame_fence, null); 241 | } 242 | 243 | fn waitForFence(self: SwapImage, gc: *const GraphicsContext) !void { 244 | _ = try gc.dev.waitForFences(1, @ptrCast(&self.frame_fence), vk.TRUE, std.math.maxInt(u64)); 245 | } 246 | }; 247 | 248 | fn initSwapchainImages(gc: *const GraphicsContext, swapchain: vk.SwapchainKHR, format: vk.Format, allocator: Allocator) ![]SwapImage { 249 | const images = try gc.dev.getSwapchainImagesAllocKHR(swapchain, allocator); 250 | defer allocator.free(images); 251 | 252 | const swap_images = try allocator.alloc(SwapImage, images.len); 253 | errdefer allocator.free(swap_images); 254 | 255 | var i: usize = 0; 256 | errdefer for (swap_images[0..i]) |si| si.deinit(gc); 257 | 258 | for (images) |image| { 259 | swap_images[i] = try SwapImage.init(gc, image, format); 260 | i += 1; 261 | } 262 | 263 | return swap_images; 264 | } 265 | 266 | fn findSurfaceFormat(gc: *const GraphicsContext, allocator: Allocator) !vk.SurfaceFormatKHR { 267 | const preferred = vk.SurfaceFormatKHR{ 268 | .format = .b8g8r8a8_srgb, 269 | .color_space = .srgb_nonlinear_khr, 270 | }; 271 | 272 | const surface_formats = try gc.instance.getPhysicalDeviceSurfaceFormatsAllocKHR(gc.pdev, gc.surface, allocator); 273 | defer allocator.free(surface_formats); 274 | 275 | for (surface_formats) |sfmt| { 276 | if (std.meta.eql(sfmt, preferred)) { 277 | return preferred; 278 | } 279 | } 280 | 281 | return surface_formats[0]; // There must always be at least one supported surface format 282 | } 283 | 284 | fn findPresentMode(gc: *const GraphicsContext, allocator: Allocator) !vk.PresentModeKHR { 285 | const present_modes = try gc.instance.getPhysicalDeviceSurfacePresentModesAllocKHR(gc.pdev, gc.surface, allocator); 286 | defer allocator.free(present_modes); 287 | 288 | const preferred = [_]vk.PresentModeKHR{ 289 | .mailbox_khr, 290 | .immediate_khr, 291 | }; 292 | 293 | for (preferred) |mode| { 294 | if (std.mem.indexOfScalar(vk.PresentModeKHR, present_modes, mode) != null) { 295 | return mode; 296 | } 297 | } 298 | 299 | return .fifo_khr; 300 | } 301 | 302 | fn findActualExtent(caps: vk.SurfaceCapabilitiesKHR, extent: vk.Extent2D) vk.Extent2D { 303 | if (caps.current_extent.width != 0xFFFF_FFFF) { 304 | return caps.current_extent; 305 | } else { 306 | return .{ 307 | .width = std.math.clamp(extent.width, caps.min_image_extent.width, caps.max_image_extent.width), 308 | .height = std.math.clamp(extent.height, caps.min_image_extent.height, caps.max_image_extent.height), 309 | }; 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const shader_triangle_vert = @embedFile("shaders.triangle.vert"); 6 | const shader_triangle_frag = @embedFile("shaders.triangle.frag"); 7 | const xcb = @import("xcb.zig"); 8 | const GraphicsContext = @import("GraphicsContext.zig"); 9 | const Swapchain = @import("Swapchain.zig"); 10 | 11 | const vk = @import("vulkan"); 12 | pub const xcb_connection_t = xcb.connection_t; 13 | pub const xcb_visualid_t = xcb.visualid_t; 14 | pub const xcb_window_t = xcb.window_t; 15 | 16 | const app_name = "vulkan-zig triangle example"; 17 | 18 | const enable_validation_layers = builtin.mode == .Debug; 19 | 20 | const Vertex = struct { 21 | const binding_description = vk.VertexInputBindingDescription{ 22 | .binding = 0, 23 | .stride = @sizeOf(Vertex), 24 | .input_rate = .vertex, 25 | }; 26 | 27 | const attribute_description = [_]vk.VertexInputAttributeDescription{ 28 | .{ 29 | .binding = 0, 30 | .location = 0, 31 | .format = .r32g32_sfloat, 32 | .offset = @offsetOf(Vertex, "pos"), 33 | }, 34 | .{ 35 | .binding = 0, 36 | .location = 1, 37 | .format = .r32g32b32_sfloat, 38 | .offset = @offsetOf(Vertex, "color"), 39 | }, 40 | }; 41 | 42 | pos: [2]f32, 43 | color: [3]f32, 44 | }; 45 | 46 | const vertices = [_]Vertex{ 47 | .{ .pos = .{ 0, -0.5 }, .color = .{ 1, 0, 0 } }, 48 | .{ .pos = .{ 0.5, 0.5 }, .color = .{ 0, 1, 0 } }, 49 | .{ .pos = .{ -0.5, 0.5 }, .color = .{ 0, 0, 1 } }, 50 | }; 51 | 52 | pub fn main() !void { 53 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; 54 | defer _ = general_purpose_allocator.deinit(); 55 | const gpa = general_purpose_allocator.allocator(); 56 | 57 | var extent: vk.Extent2D = .{ .width = 800, .height = 600 }; 58 | 59 | std.log.debug("connecting to X11", .{}); 60 | 61 | var scr: c_int = undefined; 62 | const connection = xcb.connect(null, &scr).?; 63 | if (xcb.connection_has_error(connection) != 0) { 64 | @panic("Could not find a compatible Vulkan ICD"); 65 | } 66 | const setup = xcb.get_setup(connection); 67 | var iter = xcb.setup_roots_iterator(setup); 68 | while (scr > 0) : (scr -= 1) { 69 | xcb.screen_next(&iter); 70 | } 71 | const screen = iter.data; 72 | 73 | std.log.debug("opening window", .{}); 74 | const window = xcb.generate_id(connection); 75 | const value_mask = xcb.CW.BACK_PIXEL | xcb.CW.EVENT_MASK; 76 | const value_list = [_]u32{ 77 | screen.black_pixel, 78 | xcb.EVENT_MASK.KEY_RELEASE | 79 | xcb.EVENT_MASK.KEY_PRESS | 80 | xcb.EVENT_MASK.EXPOSURE | 81 | xcb.EVENT_MASK.STRUCTURE_NOTIFY | 82 | xcb.EVENT_MASK.POINTER_MOTION | 83 | xcb.EVENT_MASK.BUTTON_PRESS | 84 | xcb.EVENT_MASK.BUTTON_RELEASE, 85 | }; 86 | 87 | _ = xcb.create_window( 88 | connection, 89 | xcb.COPY_FROM_PARENT, 90 | window, 91 | screen.root, 92 | 0, 93 | 0, 94 | @intCast(extent.width), 95 | @intCast(extent.height), 96 | 0, 97 | @intFromEnum(xcb.window_class_t.INPUT_OUTPUT), 98 | screen.root_visual, 99 | value_mask, 100 | &value_list, 101 | ); 102 | 103 | // Send notification when window is destroyed. 104 | const atom_wm_protocols = try get_atom(connection, "WM_PROTOCOLS"); 105 | const atom_wm_delete_window = try get_atom(connection, "WM_DELETE_WINDOW"); 106 | _ = xcb.change_property( 107 | connection, 108 | .REPLACE, 109 | window, 110 | atom_wm_protocols, 111 | .ATOM, 112 | 32, 113 | 1, 114 | &atom_wm_delete_window, 115 | ); 116 | 117 | const title = "Zig Vulkan Triangle"; 118 | _ = xcb.change_property(connection, .REPLACE, window, .WM_NAME, .STRING, 8, title.len, title); 119 | 120 | // Set the WM_CLASS property to display title in dash tooltip and 121 | // application menu on GNOME and other desktop environments 122 | var wm_class_buf: [100]u8 = undefined; 123 | const wm_class = std.fmt.bufPrint(&wm_class_buf, "windowName\x00{s}\x00", .{title}) catch unreachable; 124 | _ = xcb.change_property( 125 | connection, 126 | .REPLACE, 127 | window, 128 | .WM_CLASS, 129 | .STRING, 130 | 8, 131 | @intCast(wm_class.len), 132 | wm_class.ptr, 133 | ); 134 | _ = xcb.map_window(connection, window); 135 | 136 | const gc = try GraphicsContext.init(gpa, app_name, connection, window, enable_validation_layers); 137 | defer gc.deinit(); 138 | 139 | std.log.debug("Using device: {s}", .{gc.deviceName()}); 140 | 141 | var swapchain = try Swapchain.init(&gc, gpa, extent); 142 | defer swapchain.deinit(); 143 | 144 | const pipeline_layout = try gc.dev.createPipelineLayout(&.{ 145 | .flags = .{}, 146 | .set_layout_count = 0, 147 | .p_set_layouts = undefined, 148 | .push_constant_range_count = 0, 149 | .p_push_constant_ranges = undefined, 150 | }, null); 151 | defer gc.dev.destroyPipelineLayout(pipeline_layout, null); 152 | 153 | const render_pass = try createRenderPass(&gc, swapchain); 154 | defer gc.dev.destroyRenderPass(render_pass, null); 155 | 156 | const pipeline = try createPipeline(&gc, pipeline_layout, render_pass); 157 | defer gc.dev.destroyPipeline(pipeline, null); 158 | 159 | var framebuffers = try createFramebuffers(&gc, gpa, render_pass, swapchain); 160 | defer destroyFramebuffers(&gc, gpa, framebuffers); 161 | 162 | const pool = try gc.dev.createCommandPool(&.{ 163 | .queue_family_index = gc.graphics_queue.family, 164 | }, null); 165 | defer gc.dev.destroyCommandPool(pool, null); 166 | 167 | const buffer = try gc.dev.createBuffer(&.{ 168 | .size = @sizeOf(@TypeOf(vertices)), 169 | .usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true }, 170 | .sharing_mode = .exclusive, 171 | }, null); 172 | defer gc.dev.destroyBuffer(buffer, null); 173 | const mem_reqs = gc.dev.getBufferMemoryRequirements(buffer); 174 | const memory = try gc.allocate(mem_reqs, .{ .device_local_bit = true }); 175 | defer gc.dev.freeMemory(memory, null); 176 | try gc.dev.bindBufferMemory(buffer, memory, 0); 177 | 178 | try uploadVertices(&gc, pool, buffer); 179 | 180 | var cmdbufs = try createCommandBuffers( 181 | &gc, 182 | pool, 183 | gpa, 184 | buffer, 185 | swapchain.extent, 186 | render_pass, 187 | pipeline, 188 | framebuffers, 189 | ); 190 | defer destroyCommandBuffers(&gc, pool, gpa, cmdbufs); 191 | 192 | defer { 193 | swapchain.waitForAllFences() catch {}; 194 | gc.dev.deviceWaitIdle() catch {}; 195 | } 196 | 197 | std.log.debug("main loop", .{}); 198 | 199 | while (true) { 200 | var extent_changed = false; 201 | var opt_event = xcb.wait_for_event(connection); 202 | while (opt_event) |event| : (opt_event = xcb.poll_for_event(connection)) { 203 | defer std.c.free(event); 204 | switch (event.response_type.op) { 205 | .CLIENT_MESSAGE => blk: { 206 | const client_message: *xcb.client_message_event_t = @ptrCast(event); 207 | if (client_message.window != window) break :blk; 208 | 209 | if (client_message.type == atom_wm_protocols) { 210 | const msg_atom: xcb.atom_t = @enumFromInt(client_message.data.data32[0]); 211 | if (msg_atom == atom_wm_delete_window) return std.process.cleanExit(); 212 | } else if (client_message.type == .NOTICE) { 213 | // We repaint every frame regardless. 214 | } 215 | }, 216 | .CONFIGURE_NOTIFY => { 217 | const configure: *xcb.configure_notify_event_t = @ptrCast(event); 218 | if (extent.width != configure.width or 219 | extent.height != configure.height) 220 | { 221 | extent.width = configure.width; 222 | extent.height = configure.height; 223 | extent_changed = true; 224 | } 225 | }, 226 | .EXPOSE => { 227 | // We paint everything every frame, so this message is pointless. 228 | }, 229 | .KEY_PRESS => { 230 | const key_press: *xcb.key_press_event_t = @ptrCast(event); 231 | if (key_press.detail == 9) return std.process.cleanExit(); 232 | }, 233 | .KEY_RELEASE => { 234 | // key up 235 | }, 236 | .MOTION_NOTIFY => { 237 | // mouse movement 238 | }, 239 | .BUTTON_PRESS => { 240 | // mouse down 241 | }, 242 | .BUTTON_RELEASE => { 243 | // mouse up 244 | }, 245 | else => |t| { 246 | std.log.debug("unhandled xcb message: {s}", .{@tagName(t)}); 247 | }, 248 | } 249 | } 250 | 251 | const cmdbuf = cmdbufs[swapchain.image_index]; 252 | 253 | const state = swapchain.present(cmdbuf) catch |err| switch (err) { 254 | error.OutOfDateKHR => Swapchain.PresentState.suboptimal, 255 | else => |narrow| return narrow, 256 | }; 257 | 258 | if (state == .suboptimal or extent_changed) { 259 | try swapchain.recreate(extent); 260 | 261 | destroyFramebuffers(&gc, gpa, framebuffers); 262 | framebuffers = try createFramebuffers(&gc, gpa, render_pass, swapchain); 263 | 264 | destroyCommandBuffers(&gc, pool, gpa, cmdbufs); 265 | cmdbufs = try createCommandBuffers( 266 | &gc, 267 | pool, 268 | gpa, 269 | buffer, 270 | swapchain.extent, 271 | render_pass, 272 | pipeline, 273 | framebuffers, 274 | ); 275 | } 276 | } 277 | } 278 | 279 | fn uploadVertices(gc: *const GraphicsContext, pool: vk.CommandPool, buffer: vk.Buffer) !void { 280 | const staging_buffer = try gc.dev.createBuffer(&.{ 281 | .size = @sizeOf(@TypeOf(vertices)), 282 | .usage = .{ .transfer_src_bit = true }, 283 | .sharing_mode = .exclusive, 284 | }, null); 285 | defer gc.dev.destroyBuffer(staging_buffer, null); 286 | const mem_reqs = gc.dev.getBufferMemoryRequirements(staging_buffer); 287 | const staging_memory = try gc.allocate(mem_reqs, .{ .host_visible_bit = true, .host_coherent_bit = true }); 288 | defer gc.dev.freeMemory(staging_memory, null); 289 | try gc.dev.bindBufferMemory(staging_buffer, staging_memory, 0); 290 | 291 | { 292 | const data = try gc.dev.mapMemory(staging_memory, 0, vk.WHOLE_SIZE, .{}); 293 | defer gc.dev.unmapMemory(staging_memory); 294 | 295 | const gpu_vertices: [*]Vertex = @ptrCast(@alignCast(data)); 296 | @memcpy(gpu_vertices, vertices[0..]); 297 | } 298 | 299 | try copyBuffer(gc, pool, buffer, staging_buffer, @sizeOf(@TypeOf(vertices))); 300 | } 301 | 302 | fn copyBuffer(gc: *const GraphicsContext, pool: vk.CommandPool, dst: vk.Buffer, src: vk.Buffer, size: vk.DeviceSize) !void { 303 | var cmdbuf_handle: vk.CommandBuffer = undefined; 304 | try gc.dev.allocateCommandBuffers(&.{ 305 | .command_pool = pool, 306 | .level = .primary, 307 | .command_buffer_count = 1, 308 | }, @ptrCast(&cmdbuf_handle)); 309 | defer gc.dev.freeCommandBuffers(pool, 1, @ptrCast(&cmdbuf_handle)); 310 | 311 | const cmdbuf = GraphicsContext.CommandBuffer.init(cmdbuf_handle, gc.dev.wrapper); 312 | 313 | try cmdbuf.beginCommandBuffer(&.{ 314 | .flags = .{ .one_time_submit_bit = true }, 315 | }); 316 | 317 | const region = vk.BufferCopy{ 318 | .src_offset = 0, 319 | .dst_offset = 0, 320 | .size = size, 321 | }; 322 | cmdbuf.copyBuffer(src, dst, 1, @ptrCast(®ion)); 323 | 324 | try cmdbuf.endCommandBuffer(); 325 | 326 | const si = vk.SubmitInfo{ 327 | .command_buffer_count = 1, 328 | .p_command_buffers = (&cmdbuf.handle)[0..1], 329 | .p_wait_dst_stage_mask = undefined, 330 | }; 331 | try gc.dev.queueSubmit(gc.graphics_queue.handle, 1, @ptrCast(&si), .null_handle); 332 | try gc.dev.queueWaitIdle(gc.graphics_queue.handle); 333 | } 334 | 335 | fn createCommandBuffers( 336 | gc: *const GraphicsContext, 337 | pool: vk.CommandPool, 338 | gpa: Allocator, 339 | buffer: vk.Buffer, 340 | extent: vk.Extent2D, 341 | render_pass: vk.RenderPass, 342 | pipeline: vk.Pipeline, 343 | framebuffers: []vk.Framebuffer, 344 | ) ![]vk.CommandBuffer { 345 | const cmdbufs = try gpa.alloc(vk.CommandBuffer, framebuffers.len); 346 | errdefer gpa.free(cmdbufs); 347 | 348 | try gc.dev.allocateCommandBuffers(&.{ 349 | .command_pool = pool, 350 | .level = .primary, 351 | .command_buffer_count = @intCast(cmdbufs.len), 352 | }, cmdbufs.ptr); 353 | errdefer gc.dev.freeCommandBuffers(pool, @intCast(cmdbufs.len), cmdbufs.ptr); 354 | 355 | const clear: vk.ClearValue = .{ 356 | .color = .{ .float_32 = .{ 0, 0, 0, 1 } }, 357 | }; 358 | 359 | const viewport: vk.Viewport = .{ 360 | .x = 0, 361 | .y = 0, 362 | .width = @floatFromInt(extent.width), 363 | .height = @floatFromInt(extent.height), 364 | .min_depth = 0, 365 | .max_depth = 1, 366 | }; 367 | 368 | const scissor: vk.Rect2D = .{ 369 | .offset = .{ .x = 0, .y = 0 }, 370 | .extent = extent, 371 | }; 372 | 373 | for (cmdbufs, framebuffers) |cmdbuf, framebuffer| { 374 | try gc.dev.beginCommandBuffer(cmdbuf, &.{}); 375 | 376 | gc.dev.cmdSetViewport(cmdbuf, 0, 1, @ptrCast(&viewport)); 377 | gc.dev.cmdSetScissor(cmdbuf, 0, 1, @ptrCast(&scissor)); 378 | 379 | const render_area: vk.Rect2D = .{ 380 | .offset = .{ .x = 0, .y = 0 }, 381 | .extent = extent, 382 | }; 383 | 384 | gc.dev.cmdBeginRenderPass(cmdbuf, &.{ 385 | .render_pass = render_pass, 386 | .framebuffer = framebuffer, 387 | .render_area = render_area, 388 | .clear_value_count = 1, 389 | .p_clear_values = @ptrCast(&clear), 390 | }, .@"inline"); 391 | 392 | gc.dev.cmdBindPipeline(cmdbuf, .graphics, pipeline); 393 | const offset = [_]vk.DeviceSize{0}; 394 | gc.dev.cmdBindVertexBuffers(cmdbuf, 0, 1, @ptrCast(&buffer), &offset); 395 | gc.dev.cmdDraw(cmdbuf, vertices.len, 1, 0, 0); 396 | 397 | gc.dev.cmdEndRenderPass(cmdbuf); 398 | try gc.dev.endCommandBuffer(cmdbuf); 399 | } 400 | 401 | return cmdbufs; 402 | } 403 | 404 | fn destroyCommandBuffers(gc: *const GraphicsContext, pool: vk.CommandPool, gpa: Allocator, cmdbufs: []vk.CommandBuffer) void { 405 | gc.dev.freeCommandBuffers(pool, @truncate(cmdbufs.len), cmdbufs.ptr); 406 | gpa.free(cmdbufs); 407 | } 408 | 409 | fn createFramebuffers(gc: *const GraphicsContext, gpa: Allocator, render_pass: vk.RenderPass, swapchain: Swapchain) ![]vk.Framebuffer { 410 | const framebuffers = try gpa.alloc(vk.Framebuffer, swapchain.swap_images.len); 411 | errdefer gpa.free(framebuffers); 412 | 413 | var i: usize = 0; 414 | errdefer for (framebuffers[0..i]) |fb| gc.dev.destroyFramebuffer(fb, null); 415 | 416 | for (framebuffers) |*fb| { 417 | fb.* = try gc.dev.createFramebuffer(&.{ 418 | .render_pass = render_pass, 419 | .attachment_count = 1, 420 | .p_attachments = @ptrCast(&swapchain.swap_images[i].view), 421 | .width = swapchain.extent.width, 422 | .height = swapchain.extent.height, 423 | .layers = 1, 424 | }, null); 425 | i += 1; 426 | } 427 | 428 | return framebuffers; 429 | } 430 | 431 | fn destroyFramebuffers(gc: *const GraphicsContext, gpa: Allocator, framebuffers: []const vk.Framebuffer) void { 432 | for (framebuffers) |fb| gc.dev.destroyFramebuffer(fb, null); 433 | gpa.free(framebuffers); 434 | } 435 | 436 | fn createRenderPass(gc: *const GraphicsContext, swapchain: Swapchain) !vk.RenderPass { 437 | const color_attachment: vk.AttachmentDescription = .{ 438 | .format = swapchain.surface_format.format, 439 | .samples = .{ .@"1_bit" = true }, 440 | .load_op = .clear, 441 | .store_op = .store, 442 | .stencil_load_op = .dont_care, 443 | .stencil_store_op = .dont_care, 444 | .initial_layout = .undefined, 445 | .final_layout = .present_src_khr, 446 | }; 447 | 448 | const color_attachment_ref: vk.AttachmentReference = .{ 449 | .attachment = 0, 450 | .layout = .color_attachment_optimal, 451 | }; 452 | 453 | const subpass: vk.SubpassDescription = .{ 454 | .pipeline_bind_point = .graphics, 455 | .color_attachment_count = 1, 456 | .p_color_attachments = @ptrCast(&color_attachment_ref), 457 | }; 458 | 459 | return try gc.dev.createRenderPass(&.{ 460 | .attachment_count = 1, 461 | .p_attachments = @ptrCast(&color_attachment), 462 | .subpass_count = 1, 463 | .p_subpasses = @ptrCast(&subpass), 464 | }, null); 465 | } 466 | 467 | fn createPipeline( 468 | gc: *const GraphicsContext, 469 | layout: vk.PipelineLayout, 470 | render_pass: vk.RenderPass, 471 | ) !vk.Pipeline { 472 | const vert = try gc.dev.createShaderModule(&.{ 473 | .code_size = shader_triangle_vert.len, 474 | .p_code = @alignCast(@ptrCast(shader_triangle_vert)), 475 | }, null); 476 | defer gc.dev.destroyShaderModule(vert, null); 477 | 478 | const frag = try gc.dev.createShaderModule(&.{ 479 | .code_size = shader_triangle_frag.len, 480 | .p_code = @alignCast(@ptrCast(shader_triangle_frag)), 481 | }, null); 482 | defer gc.dev.destroyShaderModule(frag, null); 483 | 484 | const pssci = [_]vk.PipelineShaderStageCreateInfo{ 485 | .{ 486 | .stage = .{ .vertex_bit = true }, 487 | .module = vert, 488 | .p_name = "main", 489 | }, 490 | .{ 491 | .stage = .{ .fragment_bit = true }, 492 | .module = frag, 493 | .p_name = "main", 494 | }, 495 | }; 496 | 497 | const pvisci = vk.PipelineVertexInputStateCreateInfo{ 498 | .vertex_binding_description_count = 1, 499 | .p_vertex_binding_descriptions = @ptrCast(&Vertex.binding_description), 500 | .vertex_attribute_description_count = Vertex.attribute_description.len, 501 | .p_vertex_attribute_descriptions = &Vertex.attribute_description, 502 | }; 503 | 504 | const piasci = vk.PipelineInputAssemblyStateCreateInfo{ 505 | .topology = .triangle_list, 506 | .primitive_restart_enable = vk.FALSE, 507 | }; 508 | 509 | const pvsci = vk.PipelineViewportStateCreateInfo{ 510 | .viewport_count = 1, 511 | .p_viewports = undefined, // set in createCommandBuffers with cmdSetViewport 512 | .scissor_count = 1, 513 | .p_scissors = undefined, // set in createCommandBuffers with cmdSetScissor 514 | }; 515 | 516 | const prsci = vk.PipelineRasterizationStateCreateInfo{ 517 | .depth_clamp_enable = vk.FALSE, 518 | .rasterizer_discard_enable = vk.FALSE, 519 | .polygon_mode = .fill, 520 | .cull_mode = .{ .back_bit = true }, 521 | .front_face = .clockwise, 522 | .depth_bias_enable = vk.FALSE, 523 | .depth_bias_constant_factor = 0, 524 | .depth_bias_clamp = 0, 525 | .depth_bias_slope_factor = 0, 526 | .line_width = 1, 527 | }; 528 | 529 | const pmsci = vk.PipelineMultisampleStateCreateInfo{ 530 | .rasterization_samples = .{ .@"1_bit" = true }, 531 | .sample_shading_enable = vk.FALSE, 532 | .min_sample_shading = 1, 533 | .alpha_to_coverage_enable = vk.FALSE, 534 | .alpha_to_one_enable = vk.FALSE, 535 | }; 536 | 537 | const pcbas = vk.PipelineColorBlendAttachmentState{ 538 | .blend_enable = vk.FALSE, 539 | .src_color_blend_factor = .one, 540 | .dst_color_blend_factor = .zero, 541 | .color_blend_op = .add, 542 | .src_alpha_blend_factor = .one, 543 | .dst_alpha_blend_factor = .zero, 544 | .alpha_blend_op = .add, 545 | .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true }, 546 | }; 547 | 548 | const pcbsci = vk.PipelineColorBlendStateCreateInfo{ 549 | .logic_op_enable = vk.FALSE, 550 | .logic_op = .copy, 551 | .attachment_count = 1, 552 | .p_attachments = @ptrCast(&pcbas), 553 | .blend_constants = [_]f32{ 0, 0, 0, 0 }, 554 | }; 555 | 556 | const dynstate = [_]vk.DynamicState{ .viewport, .scissor }; 557 | const pdsci = vk.PipelineDynamicStateCreateInfo{ 558 | .flags = .{}, 559 | .dynamic_state_count = dynstate.len, 560 | .p_dynamic_states = &dynstate, 561 | }; 562 | 563 | const gpci = vk.GraphicsPipelineCreateInfo{ 564 | .flags = .{}, 565 | .stage_count = 2, 566 | .p_stages = &pssci, 567 | .p_vertex_input_state = &pvisci, 568 | .p_input_assembly_state = &piasci, 569 | .p_tessellation_state = null, 570 | .p_viewport_state = &pvsci, 571 | .p_rasterization_state = &prsci, 572 | .p_multisample_state = &pmsci, 573 | .p_depth_stencil_state = null, 574 | .p_color_blend_state = &pcbsci, 575 | .p_dynamic_state = &pdsci, 576 | .layout = layout, 577 | .render_pass = render_pass, 578 | .subpass = 0, 579 | .base_pipeline_handle = .null_handle, 580 | .base_pipeline_index = -1, 581 | }; 582 | 583 | var pipeline: vk.Pipeline = undefined; 584 | _ = try gc.dev.createGraphicsPipelines( 585 | .null_handle, 586 | 1, 587 | @ptrCast(&gpci), 588 | null, 589 | @ptrCast(&pipeline), 590 | ); 591 | return pipeline; 592 | } 593 | 594 | fn get_atom(conn: *xcb.connection_t, name: [:0]const u8) error{OutOfMemory}!xcb.atom_t { 595 | const cookie = xcb.intern_atom(conn, 0, @intCast(name.len), name.ptr); 596 | if (xcb.intern_atom_reply(conn, cookie, null)) |r| { 597 | defer std.c.free(r); 598 | return r.atom; 599 | } 600 | return error.OutOfMemory; 601 | } 602 | -------------------------------------------------------------------------------- /src/xcb.zig: -------------------------------------------------------------------------------- 1 | extern "xcb" fn xcb_connect(displayname: ?[*:0]const u8, screenp: *c_int) callconv(.C) ?*connection_t; 2 | pub const connect = xcb_connect; 3 | 4 | extern "xcb" fn xcb_get_setup(c: *connection_t) callconv(.C) *const xcb_setup_t; 5 | pub const get_setup = xcb_get_setup; 6 | 7 | extern "xcb" fn xcb_connection_has_error(c: *connection_t) callconv(.C) c_int; 8 | pub const connection_has_error = xcb_connection_has_error; 9 | 10 | extern "xcb" fn xcb_setup_roots_iterator(R: *const xcb_setup_t) callconv(.C) xcb_screen_iterator_t; 11 | pub const setup_roots_iterator = xcb_setup_roots_iterator; 12 | 13 | extern "xcb" fn xcb_screen_next(i: *xcb_screen_iterator_t) callconv(.C) void; 14 | pub const screen_next = xcb_screen_next; 15 | 16 | extern "xcb" fn xcb_generate_id(c: *connection_t) callconv(.C) u32; 17 | pub const generate_id = xcb_generate_id; 18 | 19 | extern "xcb" fn xcb_create_window( 20 | c: *connection_t, 21 | depth: u8, 22 | wid: window_t, 23 | parent: window_t, 24 | x: i16, 25 | y: i16, 26 | width: u16, 27 | height: u16, 28 | border_width: u16, 29 | _class: u16, 30 | visual: visualid_t, 31 | value_mask: u32, 32 | value_list: ?*const anyopaque, 33 | ) callconv(.C) xcb_void_cookie_t; 34 | pub const create_window = xcb_create_window; 35 | 36 | pub const wait_for_event = @extern(*const fn (c: *connection_t) callconv(.C) ?*generic_event_t, .{ 37 | .name = "xcb_wait_for_event", 38 | .library_name = "xcb", 39 | }); 40 | pub const poll_for_event = @extern(*const fn (c: *connection_t) callconv(.C) ?*generic_event_t, .{ 41 | .name = "xcb_poll_for_event", 42 | .library_name = "xcb", 43 | }); 44 | pub const intern_atom = @extern(*const InternAtomFn, .{ 45 | .name = "xcb_intern_atom", 46 | .library_name = "xcb", 47 | }); 48 | 49 | extern "xcb" fn xcb_intern_atom_reply( 50 | c: *connection_t, 51 | cookie: intern_atom_cookie_t, 52 | e: ?**xcb_generic_error_t, 53 | ) callconv(.C) ?*intern_atom_reply_t; 54 | pub const intern_atom_reply = xcb_intern_atom_reply; 55 | 56 | extern "xcb" fn xcb_change_property( 57 | c: *connection_t, 58 | mode: prop_mode_t, 59 | window: window_t, 60 | property: atom_t, 61 | type: atom_t, 62 | format: u8, 63 | data_len: u32, 64 | data: ?*const anyopaque, 65 | ) callconv(.C) xcb_void_cookie_t; 66 | pub const change_property = xcb_change_property; 67 | 68 | extern "xcb" fn xcb_map_window(c: *connection_t, window: window_t) callconv(.C) xcb_void_cookie_t; 69 | pub const map_window = xcb_map_window; 70 | 71 | pub const InternAtomFn = fn ( 72 | c: *connection_t, 73 | only_if_exists: u8, 74 | name_len: u16, 75 | name: [*:0]const u8, 76 | ) callconv(.C) intern_atom_cookie_t; 77 | 78 | pub const InternAtomReplyFn = fn ( 79 | c: *connection_t, 80 | cookie: intern_atom_cookie_t, 81 | e: ?**xcb_generic_error_t, 82 | ) callconv(.C) *intern_atom_reply_t; 83 | 84 | pub const connection_t = opaque {}; 85 | pub const keycode_t = u8; 86 | pub const window_t = u32; 87 | pub const colormap_t = u32; 88 | pub const visualid_t = u32; 89 | pub const timestamp_t = u32; 90 | 91 | pub const CW = struct { 92 | pub const BACK_PIXMAP = 1; 93 | pub const BACK_PIXEL = 2; 94 | pub const BORDER_PIXMAP = 4; 95 | pub const BORDER_PIXEL = 8; 96 | pub const BIT_GRAVITY = 16; 97 | pub const WIN_GRAVITY = 32; 98 | pub const BACKING_STORE = 64; 99 | pub const BACKING_PLANES = 128; 100 | pub const BACKING_PIXEL = 256; 101 | pub const OVERRIDE_REDIRECT = 512; 102 | pub const SAVE_UNDER = 1024; 103 | pub const EVENT_MASK = 2048; 104 | pub const DONT_PROPAGATE = 4096; 105 | pub const COLORMAP = 8192; 106 | pub const CURSOR = 16384; 107 | }; 108 | 109 | pub const EVENT_MASK = struct { 110 | pub const NO_EVENT = 0; 111 | pub const KEY_PRESS = 1; 112 | pub const KEY_RELEASE = 2; 113 | pub const BUTTON_PRESS = 4; 114 | pub const BUTTON_RELEASE = 8; 115 | pub const ENTER_WINDOW = 16; 116 | pub const LEAVE_WINDOW = 32; 117 | pub const POINTER_MOTION = 64; 118 | pub const POINTER_MOTION_HINT = 128; 119 | pub const BUTTON_1_MOTION = 256; 120 | pub const BUTTON_2_MOTION = 512; 121 | pub const BUTTON_3_MOTION = 1024; 122 | pub const BUTTON_4_MOTION = 2048; 123 | pub const BUTTON_5_MOTION = 4096; 124 | pub const BUTTON_MOTION = 8192; 125 | pub const KEYMAP_STATE = 16384; 126 | pub const EXPOSURE = 32768; 127 | pub const VISIBILITY_CHANGE = 65536; 128 | pub const STRUCTURE_NOTIFY = 131072; 129 | pub const RESIZE_REDIRECT = 262144; 130 | pub const SUBSTRUCTURE_NOTIFY = 524288; 131 | pub const SUBSTRUCTURE_REDIRECT = 1048576; 132 | pub const FOCUS_CHANGE = 2097152; 133 | pub const PROPERTY_CHANGE = 4194304; 134 | pub const COLOR_MAP_CHANGE = 8388608; 135 | pub const OWNER_GRAB_BUTTON = 16777216; 136 | }; 137 | 138 | pub const COPY_FROM_PARENT: c_long = 0; 139 | 140 | pub const intern_atom_reply_t = extern struct { 141 | response_type: u8, 142 | pad0: u8, 143 | sequence: u16, 144 | length: u32, 145 | atom: atom_t, 146 | }; 147 | 148 | const xcb_generic_error_t = extern struct { 149 | response_type: u8, 150 | error_code: u8, 151 | sequence: u16, 152 | resource_id: u32, 153 | minor_code: u16, 154 | major_code: u8, 155 | pad0: u8, 156 | pad: [5]u32, 157 | full_sequence: u32, 158 | }; 159 | 160 | pub const intern_atom_cookie_t = extern struct { 161 | sequence: c_uint, 162 | }; 163 | 164 | const xcb_void_cookie_t = extern struct { 165 | sequence: c_uint, 166 | }; 167 | 168 | const xcb_setup_t = extern struct { 169 | status: u8, 170 | pad0: u8, 171 | protocol_major_version: u16, 172 | protocol_minor_version: u16, 173 | length: u16, 174 | release_number: u32, 175 | resource_id_base: u32, 176 | resource_id_mask: u32, 177 | motion_buffer_size: u32, 178 | vendor_len: u16, 179 | maximum_request_length: u16, 180 | roots_len: u8, 181 | pixmap_formats_len: u8, 182 | image_byte_order: u8, 183 | bitmap_format_bit_order: u8, 184 | bitmap_format_scanline_unit: u8, 185 | bitmap_format_scanline_pad: u8, 186 | min_keycode: keycode_t, 187 | max_keycode: keycode_t, 188 | pad1: [4]u8, 189 | }; 190 | 191 | const xcb_screen_iterator_t = extern struct { 192 | data: *xcb_screen_t, 193 | rem: c_int, 194 | index: c_int, 195 | }; 196 | const xcb_screen_t = extern struct { 197 | root: window_t, 198 | default_colormap: colormap_t, 199 | white_pixel: u32, 200 | black_pixel: u32, 201 | current_input_masks: u32, 202 | width_in_pixels: u16, 203 | height_in_pixels: u16, 204 | width_in_millimeters: u16, 205 | height_in_millimeters: u16, 206 | min_installed_maps: u16, 207 | max_installed_maps: u16, 208 | root_visual: visualid_t, 209 | backing_stores: u8, 210 | save_unders: u8, 211 | root_depth: u8, 212 | allowed_depths_len: u8, 213 | }; 214 | 215 | pub const prop_mode_t = enum(c_int) { 216 | REPLACE = 0, 217 | PREPEND = 1, 218 | APPEND = 2, 219 | _, 220 | }; 221 | 222 | pub const atom_t = enum(u32) { 223 | NONE = 0, 224 | PRIMARY = 1, 225 | SECONDARY = 2, 226 | ARC = 3, 227 | ATOM = 4, 228 | BITMAP = 5, 229 | CARDINAL = 6, 230 | COLORMAP = 7, 231 | CURSOR = 8, 232 | CUT_BUFFER0 = 9, 233 | CUT_BUFFER1 = 10, 234 | CUT_BUFFER2 = 11, 235 | CUT_BUFFER3 = 12, 236 | CUT_BUFFER4 = 13, 237 | CUT_BUFFER5 = 14, 238 | CUT_BUFFER6 = 15, 239 | CUT_BUFFER7 = 16, 240 | DRAWABLE = 17, 241 | FONT = 18, 242 | INTEGER = 19, 243 | PIXMAP = 20, 244 | POINT = 21, 245 | RECTANGLE = 22, 246 | RESOURCE_MANAGER = 23, 247 | RGB_COLOR_MAP = 24, 248 | RGB_BEST_MAP = 25, 249 | RGB_BLUE_MAP = 26, 250 | RGB_DEFAULT_MAP = 27, 251 | RGB_GRAY_MAP = 28, 252 | RGB_GREEN_MAP = 29, 253 | RGB_RED_MAP = 30, 254 | STRING = 31, 255 | VISUALID = 32, 256 | WINDOW = 33, 257 | WM_COMMAND = 34, 258 | WM_HINTS = 35, 259 | WM_CLIENT_MACHINE = 36, 260 | WM_ICON_NAME = 37, 261 | WM_ICON_SIZE = 38, 262 | WM_NAME = 39, 263 | WM_NORMAL_HINTS = 40, 264 | WM_SIZE_HINTS = 41, 265 | WM_ZOOM_HINTS = 42, 266 | MIN_SPACE = 43, 267 | NORM_SPACE = 44, 268 | MAX_SPACE = 45, 269 | END_SPACE = 46, 270 | SUPERSCRIPT_X = 47, 271 | SUPERSCRIPT_Y = 48, 272 | SUBSCRIPT_X = 49, 273 | SUBSCRIPT_Y = 50, 274 | UNDERLINE_POSITION = 51, 275 | UNDERLINE_THICKNESS = 52, 276 | STRIKEOUT_ASCENT = 53, 277 | STRIKEOUT_DESCENT = 54, 278 | ITALIC_ANGLE = 55, 279 | X_HEIGHT = 56, 280 | QUAD_WIDTH = 57, 281 | WEIGHT = 58, 282 | POINT_SIZE = 59, 283 | RESOLUTION = 60, 284 | COPYRIGHT = 61, 285 | NOTICE = 62, 286 | FONT_NAME = 63, 287 | FAMILY_NAME = 64, 288 | FULL_NAME = 65, 289 | CAP_HEIGHT = 66, 290 | WM_CLASS = 67, 291 | WM_TRANSIENT_FOR = 68, 292 | _, 293 | }; 294 | 295 | pub const window_class_t = enum(c_int) { 296 | COPY_FROM_PARENT = 0, 297 | INPUT_OUTPUT = 1, 298 | INPUT_ONLY = 2, 299 | _, 300 | }; 301 | 302 | pub const generic_event_t = extern struct { 303 | response_type: ResponseType, 304 | pad0: u8, 305 | sequence: u16, 306 | pad: [7]u32, 307 | full_sequence: u32, 308 | }; 309 | 310 | pub const ResponseType = packed struct(u8) { 311 | op: Op, 312 | mystery: u1, 313 | 314 | pub const Op = enum(u7) { 315 | KEY_PRESS = 2, 316 | KEY_RELEASE = 3, 317 | BUTTON_PRESS = 4, 318 | BUTTON_RELEASE = 5, 319 | MOTION_NOTIFY = 6, 320 | ENTER_NOTIFY = 7, 321 | LEAVE_NOTIFY = 8, 322 | FOCUS_IN = 9, 323 | FOCUS_OUT = 10, 324 | KEYMAP_NOTIFY = 11, 325 | EXPOSE = 12, 326 | GRAPHICS_EXPOSURE = 13, 327 | NO_EXPOSURE = 14, 328 | VISIBILITY_NOTIFY = 15, 329 | CREATE_NOTIFY = 16, 330 | DESTROY_NOTIFY = 17, 331 | UNMAP_NOTIFY = 18, 332 | MAP_NOTIFY = 19, 333 | MAP_REQUEST = 20, 334 | REPARENT_NOTIFY = 21, 335 | CONFIGURE_NOTIFY = 22, 336 | CONFIGURE_REQUEST = 23, 337 | GRAVITY_NOTIFY = 24, 338 | RESIZE_REQUEST = 25, 339 | CIRCULATE_NOTIFY = 26, 340 | CIRCULATE_REQUEST = 27, 341 | PROPERTY_NOTIFY = 28, 342 | SELECTION_CLEAR = 29, 343 | SELECTION_REQUEST = 30, 344 | SELECTION_NOTIFY = 31, 345 | COLORMAP_NOTIFY = 32, 346 | CLIENT_MESSAGE = 33, 347 | MAPPING_NOTIFY = 34, 348 | GE_GENERIC = 35, 349 | }; 350 | }; 351 | 352 | pub const client_message_event_t = extern struct { 353 | response_type: ResponseType, 354 | format: u8, 355 | sequence: u16, 356 | window: window_t, 357 | type: atom_t, 358 | data: client_message_data_t, 359 | }; 360 | 361 | pub const client_message_data_t = extern union { 362 | data8: [20]u8, 363 | data16: [10]u16, 364 | data32: [5]u32, 365 | }; 366 | 367 | pub const configure_notify_event_t = extern struct { 368 | response_type: ResponseType, 369 | pad0: u8, 370 | sequence: u16, 371 | event: window_t, 372 | window: window_t, 373 | above_sibling: window_t, 374 | x: i16, 375 | y: i16, 376 | width: u16, 377 | height: u16, 378 | border_width: u16, 379 | override_redirect: u8, 380 | pad1: u8, 381 | }; 382 | 383 | pub const key_press_event_t = extern struct { 384 | response_type: ResponseType, 385 | detail: keycode_t, 386 | sequence: u16, 387 | time: timestamp_t, 388 | root: window_t, 389 | event: window_t, 390 | child: window_t, 391 | root_x: i16, 392 | root_y: i16, 393 | event_x: i16, 394 | event_y: i16, 395 | state: u16, 396 | same_screen: u8, 397 | pad0: u8, 398 | }; 399 | --------------------------------------------------------------------------------