const vk = @import("vulkan"); const glfw = @import("glfw"); const log2 = @import("std").math.log2; const settings = @import("settings.zig"); const OnlyIf = settings.OnlyIf; const Extensions = @import("Extensions.zig"); const Instance = @import("instance.zig").Instance; const enabled_layers = @import("layers.zig").enabled; const Renderer = @import("Renderer.zig"); const Window = @import("display.zig").Window; const queues = @import("queues.zig"); const std = @import("std"); // TODO memory var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); const DeviceDispatch = vk.DeviceWrapper(.{ .destroyDevice = true, .getDeviceQueue = true, .createSemaphore = true, .createFence = true, .createImageView = true, .destroyImageView = true, .destroySemaphore = true, .destroyFence = true, .getSwapchainImagesKHR = true, .createSwapchainKHR = true, .destroySwapchainKHR = true, .acquireNextImageKHR = true, .deviceWaitIdle = true, .waitForFences = true, .resetFences = true, .queueSubmit = true, .queuePresentKHR = true, .createCommandPool = true, .destroyCommandPool = true, .allocateCommandBuffers = true, .freeCommandBuffers = true, .queueWaitIdle = true, .createShaderModule = true, .destroyShaderModule = true, .createPipelineLayout = true, .destroyPipelineLayout = true, .createRenderPass = true, .destroyRenderPass = true, .createGraphicsPipelines = true, .destroyPipeline = true, .createFramebuffer = true, .destroyFramebuffer = true, .beginCommandBuffer = true, .endCommandBuffer = true, .resetCommandBuffer = true, .allocateMemory = true, .freeMemory = true, .createBuffer = true, .destroyBuffer = true, .getBufferMemoryRequirements = true, .mapMemory = true, .unmapMemory = true, .bindBufferMemory = true, .cmdBeginRenderPass = true, .cmdEndRenderPass = true, .cmdBindPipeline = true, .cmdDraw = true, .cmdSetViewport = true, .cmdSetScissor = true, .cmdBindVertexBuffers = true, .cmdCopyBuffer = true, .getFenceStatus = true, }); const Props = struct { phdev: vk.PhysicalDeviceProperties2, accstr: OnlyIf(settings.rt_on, vk.PhysicalDeviceAccelerationStructurePropertiesKHR), rtpipe: OnlyIf(settings.rt_on, vk.PhysicalDeviceRayTracingPipelinePropertiesKHR), pub fn getName(self: *Props) []u8 { return std.mem.sliceTo(&self.phdev.properties.device_name, 0); } }; const Features = struct { phdev: vk.PhysicalDeviceFeatures2, accstr: OnlyIf(settings.rt_on, vk.PhysicalDeviceAccelerationStructureFeaturesKHR) = undefined, rtpipe: OnlyIf(settings.rt_on, vk.PhysicalDeviceRayTracingPipelineFeaturesKHR) = undefined, rquery: OnlyIf(settings.rt_on, vk.PhysicalDeviceRayQueryFeaturesKHR) = undefined, }; pub const Device = struct { const Self = @This(); physical_device: vk.PhysicalDevice = undefined, handle: vk.Device = undefined, dispatch: DeviceDispatch = undefined, props: Props = undefined, exts: Extensions.Device = undefined, feats: Features = undefined, pub fn init(instance: *const Instance, window: *const Window) !Self { var self = Self{}; try selectPhysicalDevice(&self, instance, window); try self.createDevice(window, instance); self.dispatch = try DeviceDispatch.load(self.handle, instance.dispatch.dispatch.vkGetDeviceProcAddr); errdefer self.dispatch.destroyDevice(self.handle, null); // TODO: volk init? try queues.init(instance, &self, window); return self; } pub fn deinit(self: *const Self) void { queues.deinit(self); self.dispatch.destroyDevice(self.handle, null); } pub fn waitIdle(self: *const Self) !void { try self.dispatch.deviceWaitIdle(self.handle); } pub fn getName(self: *Self) ![]u8 { return self.props.getName(); } const DeviceScore = struct { rt_score: i32, ext_score: i32, feat_score: i32, prop_score: i32, has_required_exts: bool, has_queue_support: bool, }; fn selectPhysicalDevice(self: *Device, instance: *const Instance, window: *const Window) !void { var device_count: u32 = undefined; _ = try instance.dispatch.enumeratePhysicalDevices(instance.handle, &device_count, null); const pdevs = try allocator.alloc(vk.PhysicalDevice, device_count); defer allocator.free(pdevs); _ = try instance.dispatch.enumeratePhysicalDevices(instance.handle, &device_count, pdevs.ptr); const prop_list = try allocator.alloc(Props, device_count); defer allocator.free(prop_list); for (prop_list) |*prop, i| { prop.* = .{ .phdev = .{ .s_type = .physical_device_properties_2, .properties = undefined, }, .accstr = undefined, .rtpipe = undefined, }; if (settings.rt_on) { prop.phdev.p_next = prop.accstr; prop.accstr = .{ .s_type = .physical_device_acceleration_structure_properties_khr, .p_next = &prop.rtpipe, }; prop.rtpipe = .{ .s_type = .physical_device_ray_tracing_pipeline_properties_khr, }; } instance.dispatch.getPhysicalDeviceProperties2(pdevs[i], &prop.phdev); } const feats_list = try allocator.alloc(Features, device_count); defer allocator.free(feats_list); for (feats_list) |*feat, i| { feat.phdev.s_type = .physical_device_features_2; if (settings.rt_on) { feat.phdev.p_next = &feat.accstr; feat.accstr.s_type = .physical_device_acceleration_structure_features_khr; feat.accstr.p_next = &feat.rtpipe; feat.rtpipe.s_type = .physical_device_ray_tracing_pipeline_features_khr; feat.rtpipe.p_next = &feat.rquery; feat.rquery.s_type = .physical_device_ray_query_features_khr; } instance.dispatch.getPhysicalDeviceFeatures2(pdevs[i], &feat.phdev); } const exts_list = try allocator.alloc(Extensions.Device, device_count); defer allocator.free(exts_list); for (exts_list) |*ext, i| { var count: u32 = undefined; _ = try instance.dispatch.enumerateDeviceExtensionProperties(pdevs[i], null, &count, null); const props = try allocator.alloc(vk.ExtensionProperties, count); defer allocator.free(props); _ = try instance.dispatch.enumerateDeviceExtensionProperties(pdevs[i], null, &count, props.ptr); ext.* = Extensions.Device.get(props); } const scores_list = try allocator.alloc(DeviceScore, device_count); defer allocator.free(scores_list); for (scores_list) |_, i| { scores_list[i].has_required_exts = hasRequired(&exts_list[i]); scores_list[i].rt_score = rtEval(&exts_list[i]); scores_list[i].ext_score = extsEval(&exts_list[i]); scores_list[i].feat_score = featsEval(&feats_list[i]); scores_list[i].prop_score = propsEval(&prop_list[i]); const queue_support = try queues.Support.init(instance, pdevs[i], window); defer queue_support.deinit(); var has_queue_support = true; for (queue_support.families) |family| { if (family == null) { has_queue_support = false; } } scores_list[i].has_queue_support = has_queue_support; } var chosen_dev: ?usize = null; for (scores_list) |score, i| { if (!score.has_required_exts) { continue; } if (!score.has_queue_support) { continue; } if (chosen_dev) |chosen| { var cmp = scores_list[chosen].rt_score - score.rt_score; if (cmp != 0) { chosen_dev = if (cmp < 0) i else chosen; continue; } cmp = scores_list[chosen].ext_score - score.ext_score; if (cmp != 0) { chosen_dev = if (cmp < 0) i else chosen; continue; } cmp = scores_list[chosen].feat_score - score.feat_score; if (cmp != 0) { chosen_dev = if (cmp < 0) i else chosen; continue; } cmp = scores_list[chosen].prop_score - score.prop_score; if (cmp != 0) { chosen_dev = if (cmp < 0) i else chosen; continue; } } else { chosen_dev = i; } } if (chosen_dev) |chosen| { self.props = prop_list[chosen]; self.feats = feats_list[chosen]; self.exts = exts_list[chosen]; self.physical_device = pdevs[chosen]; } else unreachable; } fn hasRequired(exts: *Extensions.Device) bool { var has_all = false; has_all = has_all and exts.khr_swapchain; return has_all; } fn rtEval(exts: *Extensions.Device) i32 { var score: i32 = 0; if (settings.rt_on) { score += if (exts.khr_acceleration_structure and exts.khr_ray_tracing_pipeline) 1 else 0; score += if (exts.khr_ray_query) 1 else 0; } return score; } fn extsEval(exts: *Extensions.Device) i32 { var score: i32 = 0; // https://github.com/ziglang/zig/issues/137 score += if (exts.ext_memory_budget) @as(i32, 1) else 0; score += if (exts.ext_hdr_metadata) @as(i32, 1) else 0; score += if (exts.khr_shader_float_16_int_8) @as(i32, 1) else 0; score += if (exts.khr_1_6bit_storage) @as(i32, 1) else 0; score += if (exts.khr_push_descriptor) @as(i32, 1) else 0; score += if (exts.ext_memory_priority) @as(i32, 1) else 0; score += if (exts.khr_bind_memory_2) @as(i32, 1) else 0; score += if (exts.khr_shader_float_controls) @as(i32, 1) else 0; score += if (exts.khr_spirv_1_4) @as(i32, 1) else 0; score += if (exts.ext_conditional_rendering) @as(i32, 1) else 0; score += if (exts.khr_draw_indirect_count) @as(i32, 1) else 0; return score; } fn featsEval(feats: *Features) i32 { var score: i32 = 0; // ------------------------------------------------------------------------ // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceFeatures.html // highly useful things score += if (feats.phdev.features.full_draw_index_uint_32 != 0) @as(i32, 16) else 0; score += if (feats.phdev.features.sampler_anisotropy != 0) @as(i32, 16) else 0; score += if (feats.phdev.features.texture_compression_bc != 0) @as(i32, 16) else 0; score += if (feats.phdev.features.independent_blend != 0) @as(i32, 16) else 0; // debug drawing score += if (feats.phdev.features.fill_mode_non_solid != 0) @as(i32, 2) else 0; score += if (feats.phdev.features.wide_lines != 0) @as(i32, 2) else 0; score += if (feats.phdev.features.large_points != 0) @as(i32, 2) else 0; // profiling // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkQueryPipelineStatisticFlagBits.html score += if (feats.phdev.features.pipeline_statistics_query != 0) @as(i32, 2) else 0; // shader features score += if (feats.phdev.features.fragment_stores_and_atomics != 0) @as(i32, 4) else 0; score += if (feats.phdev.features.shader_int_64 != 0) @as(i32, 4) else 0; score += if (feats.phdev.features.shader_int_16 != 0) @as(i32, 1) else 0; score += if (feats.phdev.features.shader_storage_image_extended_formats != 0) @as(i32, 4) else 0; // dynamic indexing score += if (feats.phdev.features.shader_uniform_buffer_array_dynamic_indexing != 0) @as(i32, 4) else 0; score += if (feats.phdev.features.shader_storage_buffer_array_dynamic_indexing != 0) @as(i32, 4) else 0; score += if (feats.phdev.features.shader_sampled_image_array_dynamic_indexing != 0) @as(i32, 4) else 0; score += if (feats.phdev.features.shader_storage_image_array_dynamic_indexing != 0) @as(i32, 4) else 0; score += if (feats.phdev.features.image_cube_array != 0) @as(i32, 1) else 0; // indirect and conditional rendering score += if (feats.phdev.features.full_draw_index_uint_32 != 0) @as(i32, 1) else 0; score += if (feats.phdev.features.multi_draw_indirect != 0) @as(i32, 1) else 0; score += if (feats.phdev.features.draw_indirect_first_instance != 0) @as(i32, 1) else 0; if (settings.rt_on) { // ------------------------------------------------------------------------ // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceAccelerationStructureFeaturesKHR.html // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#acceleration-structure score += if (feats.accstr.acceleration_structure != 0) @as(i32, 64) else 0; // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#vkCmdBuildAccelerationStructuresIndirectKHR //score += if (feats.accstr.accelerationStructureIndirectBuild) 16 else 0; // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#features-accelerationStructureHostCommands //score += if (feats.accstr.accelerationStructureHostCommands) 16 else 0; // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#features-descriptorBindingAccelerationStructureUpdateAfterBind //score += if (feats.accstr.descriptorBindingAccelerationStructureUpdateAfterBind) 4 else 0; // ------------------------------------------------------------------------ // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceRayTracingPipelineFeaturesKHR.html // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#ray-tracing score += if (feats.rtpipe.ray_tracing_pipeline != 0) @as(i32, 64) else 0; // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#vkCmdTraceRaysIndirectKHR //score += if (feats.rtpipe.rayTracingPipelineTraceRaysIndirect) 16 else 0; // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#ray-traversal-culling-primitive //score += if (feats.rtpipe.rayTraversalPrimitiveCulling) 16 else 0; // ------------------------------------------------------------------------ // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceRayQueryFeaturesKHR.html // https://github.com/KhronosGroup/SPIRV-Registry/blob/master/extensions/KHR/SPV_KHR_ray_query.asciidoc score += if (feats.rquery.ray_query != 0) @as(i32, 64) else 0; } return score; } fn propsEval(props: *Props) i32 { var score: i32 = 0; score += limitsEval(&props.phdev.properties.limits); if (settings.rt_on) { score += accStrEval(props.accstr); score += rtPipeEval(props.rtpipe); } return score; } fn limitsEval(lims: *const vk.PhysicalDeviceLimits) i32 { var score: u32 = 0; score += log2(lims.max_image_dimension_1d); score += log2(lims.max_image_dimension_2d); score += log2(lims.max_image_dimension_3d); score += log2(lims.max_image_dimension_cube); score += log2(lims.max_image_array_layers); score += log2(lims.max_uniform_buffer_range); score += log2(lims.max_storage_buffer_range); score += log2(lims.max_push_constants_size); score += log2(lims.max_memory_allocation_count); score += log2(lims.max_per_stage_descriptor_storage_buffers); score += log2(lims.max_per_stage_descriptor_sampled_images); score += log2(lims.max_per_stage_descriptor_storage_images); score += log2(lims.max_per_stage_resources); score += log2(lims.max_vertex_input_attributes); score += log2(lims.max_vertex_input_bindings); score += log2(lims.max_fragment_input_components); score += log2(lims.max_compute_shared_memory_size); score += log2(lims.max_compute_work_group_invocations); score += log2(lims.max_draw_indirect_count); score += log2(lims.max_framebuffer_width); score += log2(lims.max_framebuffer_height); score += log2(lims.max_color_attachments); return @intCast(i32, score); } fn accStrEval(accstr: *const vk.PhysicalDeviceAccelerationStructurePropertiesKHR) u32 { var score: u32 = 0; score += log2(accstr.max_geometry_count); score += log2(accstr.max_instance_count); score += log2(accstr.max_primitive_count); score += log2(accstr.max_per_stage_descriptor_acceleration_structures); score += log2(accstr.max_descriptor_set_acceleration_structures); score += log2(accstr.max_descriptor_set_update_after_bind_acceleration_structures); return score; } fn rtPipeEval(rtpipe: *const vk.PhysicalDeviceRayTracingPipelineFeaturesKHR) u32 { var score: u32 = 0; score += log2(rtpipe.max_ray_recursion_depth); score += log2(rtpipe.max_ray_dispatch_invocation_count); score += log2(rtpipe.mayx_ray_hit_attribute_size); return score; } fn createDevice(self: *Self, window: *const Window, instance: *const Instance) !void { const queue_support = try queues.Support.init(instance, self.physical_device, window); defer queue_support.deinit(); var families = std.AutoArrayHashMap(u32, i32).init(allocator); defer families.deinit(); for (queue_support.families) |f| { if (f) |family| { const result = families.getOrPutAssumeCapacity(family); result.value_ptr.* = if (result.found_existing) result.value_ptr.* + 1 else 1; } } const priorities = [_]f32{ 1.0, 1.0, 1.0, 1.0 }; var queue_infos: [queues.QueueId.count]vk.DeviceQueueCreateInfo = undefined; var it = families.iterator(); { var i: usize = 0; while (it.next()) |entry| : (i += 1) { queue_infos[i] = .{ .s_type = .device_queue_create_info, .flags = .{}, .queue_family_index = @intCast(u32, entry.key_ptr.*), .queue_count = @intCast(u32, entry.value_ptr.*), .p_queue_priorities = &priorities, }; } } const ph_feats = self.feats.phdev.features; self.feats = .{ .phdev = .{ .s_type = .physical_device_features_2, .features = .{ .full_draw_index_uint_32 = ph_feats.full_draw_index_uint_32, .sampler_anisotropy = ph_feats.sampler_anisotropy, .texture_compression_bc = ph_feats.texture_compression_bc, .independent_blend = ph_feats.independent_blend, .fill_mode_non_solid = ph_feats.fill_mode_non_solid, .wide_lines = ph_feats.wide_lines, .large_points = ph_feats.large_points, .fragment_stores_and_atomics = ph_feats.fragment_stores_and_atomics, .shader_int_64 = ph_feats.shader_int_64, .shader_int_16 = ph_feats.shader_int_16, .shader_storage_image_extended_formats = ph_feats.shader_storage_image_extended_formats, .shader_uniform_buffer_array_dynamic_indexing = ph_feats.shader_uniform_buffer_array_dynamic_indexing, .shader_storage_buffer_array_dynamic_indexing = ph_feats.shader_storage_buffer_array_dynamic_indexing, .shader_sampled_image_array_dynamic_indexing = ph_feats.shader_sampled_image_array_dynamic_indexing, .shader_storage_image_array_dynamic_indexing = ph_feats.shader_storage_image_array_dynamic_indexing, .image_cube_array = ph_feats.image_cube_array, }, }, }; if (settings.rt_on) { self.props.accstr = .{ .s_type = .physical_device_acceleration_structure_features_khr, .acceleration_structure = self.props.accstr.acceleration_structure, .acceleration_structure_indirect_build = self.props.accstr.acceleration_structure_indirect_build, .acceleration_structure_host_commands = self.props.accstr.acceleration_structure_host_commands, }; self.props.rtpipe = .{ .s_type = .physical_device_ray_tracing_pipeline_features_khr, .ray_tracing_pipeline = self.props.rtpipe.ray_tracing_pipeline, .ray_tracing_pipeline_trace_rays_indirect = self.props.rtpipe.ray_tracing_pipeline_trace_rays_indirect, .ray_traversal_primitive_culling = self.props.rtpipe.ray_traversal_primitive_culling, }; self.props.rquery = .{ .s_type = .physical_device_ray_query_features_khr, .ray_query = self.props.rquery.ray_query, }; } if (settings.rt_on) { self.props.phdev.p_next = &self.props.accstr; self.props.accstr.p_next = &self.props.rtpipe; self.props.rtpipe.p_next = &self.props.rquery; } const ext_arr = Extensions.Device.toArray(allocator); defer allocator.free(ext_arr); _ = try instance.dispatch.createDevice(self.physical_device, &.{ .flags = .{}, .p_next = &self.props.phdev, .queue_create_info_count = @intCast(u32, families.count()), .p_queue_create_infos = &queue_infos, .enabled_layer_count = @intCast(u32, enabled_layers.len), .pp_enabled_layer_names = &enabled_layers, .p_enabled_features = null, .enabled_extension_count = @intCast(u32, ext_arr.len), .pp_enabled_extension_names = ext_arr.ptr, }, null); } fn getExtensions(avail_exts: *std.StringArrayHashMap) !std.ArrayList([][*:0]const u8) { const list = try std.ArrayList([*:0]const u8).init(allocator); for (try glfw.getRequiredDeviceExtensions()) |ext| { if (avail_exts.contains(ext)) { list.append(ext); } } for (Extensions.dev) |ext| { if (avail_exts.contains(ext)) { list.append(ext); } } return list; } };