const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); const builtin = @import("builtin"); const config = base.config; const SoftQueue = @import("SoftQueue.zig"); pub const SoftBinarySemaphore = @import("SoftBinarySemaphore.zig"); pub const SoftBuffer = @import("SoftBuffer.zig"); pub const SoftBufferView = @import("SoftBufferView.zig"); pub const SoftCommandBuffer = @import("SoftCommandBuffer.zig"); pub const SoftCommandPool = @import("SoftCommandPool.zig"); pub const SoftDescriptorPool = @import("SoftDescriptorPool.zig"); pub const SoftDescriptorSetLayout = @import("SoftDescriptorSetLayout.zig"); pub const SoftDeviceMemory = @import("SoftDeviceMemory.zig"); pub const SoftEvent = @import("SoftEvent.zig"); pub const SoftFence = @import("SoftFence.zig"); pub const SoftFramebuffer = @import("SoftFramebuffer.zig"); pub const SoftImage = @import("SoftImage.zig"); pub const SoftInstance = @import("SoftInstance.zig"); pub const SoftImageView = @import("SoftImageView.zig"); pub const SoftPipeline = @import("SoftPipeline.zig"); pub const SoftPipelineCache = @import("SoftPipelineCache.zig"); pub const SoftPipelineLayout = @import("SoftPipelineLayout.zig"); pub const SoftQueryPool = @import("SoftQueryPool.zig"); pub const SoftRenderPass = @import("SoftRenderPass.zig"); pub const SoftSampler = @import("SoftSampler.zig"); pub const SoftShaderModule = @import("SoftShaderModule.zig"); const VkError = base.VkError; const Self = @This(); pub const Interface = base.Device; const SpawnError = std.Thread.SpawnError; const DeviceAllocator = struct { pub inline fn allocator(_: @This()) std.mem.Allocator { return base.fallback_host_allocator; } }; interface: Interface, device_allocator: if (config.soft_debug_allocator) std.heap.DebugAllocator(.{}) else DeviceAllocator, pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; var initialized = false; errdefer { if (initialized) { self.interface.destroy(allocator) catch {}; } else { allocator.destroy(self); } } var interface = try Interface.init(allocator, instance, physical_device, info); interface.vtable = &.{ .createQueue = SoftQueue.create, .destroyQueue = SoftQueue.destroy, }; interface.dispatch_table = &.{ .allocateMemory = allocateMemory, .createBuffer = createBuffer, .createBufferView = createBufferView, .createCommandPool = createCommandPool, .createComputePipeline = createComputePipeline, .createDescriptorPool = createDescriptorPool, .createDescriptorSetLayout = createDescriptorSetLayout, .createEvent = createEvent, .createFence = createFence, .createFramebuffer = createFramebuffer, .createGraphicsPipeline = createGraphicsPipeline, .createImage = createImage, .createImageView = createImageView, .createPipelineCache = createPipelineCache, .createPipelineLayout = createPipelineLayout, .createQueryPool = createQueryPool, .createRenderPass = createRenderPass, .createSampler = createSampler, .createSemaphore = createSemaphore, .createShaderModule = createShaderModule, .destroy = destroy, .getDeviceGroupPeerMemoryFeatures = getDeviceGroupPeerMemoryFeatures, .getDeviceGroupPresentCapabilitiesKHR = getDeviceGroupPresentCapabilitiesKHR, .getDeviceGroupSurfacePresentModesKHR = getDeviceGroupSurfacePresentModesKHR, }; self.* = .{ .interface = interface, .device_allocator = if (config.soft_debug_allocator) .init else .{}, }; initialized = true; try self.interface.createQueues(allocator, info); return self; } pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); if (config.soft_debug_allocator) { // All device memory allocations should've been freed by now const leaks = self.device_allocator.detectLeaks(); if (leaks != 0) std.log.scoped(.vkDestroyDevice).err("Device was destroyed leaking {d:.3}KB", .{@as(f32, @floatFromInt(leaks)) / 1000}) else std.log.scoped(.vkDestroyDevice).debug("No device memory leaks detected", .{}); self.device_allocator.deinitWithoutLeakChecks(); } allocator.destroy(self); } pub fn allocateMemory(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.MemoryAllocateInfo) VkError!*base.DeviceMemory { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const device_memory = try SoftDeviceMemory.create(self, allocator, info.allocation_size, info.memory_type_index); return &device_memory.interface; } pub fn createBuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.BufferCreateInfo) VkError!*base.Buffer { const buffer = try SoftBuffer.create(interface, allocator, info); return &buffer.interface; } pub fn createDescriptorPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*base.DescriptorPool { const pool = try SoftDescriptorPool.create(interface, allocator, info); return &pool.interface; } pub fn createDescriptorSetLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*base.DescriptorSetLayout { const layout = try SoftDescriptorSetLayout.create(interface, allocator, info); return &layout.interface; } pub fn createFence(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*base.Fence { const fence = try SoftFence.create(interface, allocator, info); return &fence.interface; } pub fn createCommandPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.CommandPoolCreateInfo) VkError!*base.CommandPool { const pool = try SoftCommandPool.create(interface, allocator, info); return &pool.interface; } pub fn createImage(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*base.Image { const image = try SoftImage.create(interface, allocator, info); return &image.interface; } pub fn createImageView(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ImageViewCreateInfo) VkError!*base.ImageView { const view = try SoftImageView.create(interface, allocator, info); return &view.interface; } pub fn createBufferView(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.BufferViewCreateInfo) VkError!*base.BufferView { const view = try SoftBufferView.create(interface, allocator, info); return &view.interface; } pub fn createComputePipeline(interface: *Interface, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*base.Pipeline { const pipeline = try SoftPipeline.createCompute(interface, allocator, cache, info); return &pipeline.interface; } pub fn createEvent(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.EventCreateInfo) VkError!*base.Event { const event = try SoftEvent.create(interface, allocator, info); return &event.interface; } pub fn createFramebuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FramebufferCreateInfo) VkError!*base.Framebuffer { const framebuffer = try SoftFramebuffer.create(interface, allocator, info); return &framebuffer.interface; } pub fn createGraphicsPipeline(interface: *Interface, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.GraphicsPipelineCreateInfo) VkError!*base.Pipeline { const pipeline = try SoftPipeline.createGraphics(interface, allocator, cache, info); return &pipeline.interface; } pub fn createPipelineCache(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!*base.PipelineCache { const cache = try SoftPipelineCache.create(interface, allocator, info); return &cache.interface; } pub fn createPipelineLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.PipelineLayoutCreateInfo) VkError!*base.PipelineLayout { const layout = try SoftPipelineLayout.create(interface, allocator, info); return &layout.interface; } pub fn createQueryPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.QueryPoolCreateInfo) VkError!*base.QueryPool { const pool = try SoftQueryPool.create(interface, allocator, info); return &pool.interface; } pub fn createRenderPass(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.RenderPassCreateInfo) VkError!*base.RenderPass { const pass = try SoftRenderPass.create(interface, allocator, info); return &pass.interface; } pub fn createSampler(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) VkError!*base.Sampler { const sampler = try SoftSampler.create(interface, allocator, info); return &sampler.interface; } pub fn createSemaphore(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*base.BinarySemaphore { const semaphore = try SoftBinarySemaphore.create(interface, allocator, info); return &semaphore.interface; } pub fn createShaderModule(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*base.ShaderModule { const module = try SoftShaderModule.create(interface, allocator, info); return &module.interface; } pub fn getDeviceGroupPeerMemoryFeatures(interface: *Interface, heap_index: u32, local_device_index: u32, remote_device_index: u32) VkError!vk.PeerMemoryFeatureFlags { if (heap_index >= interface.physical_device.mem_props.memory_heap_count) return VkError.ValidationFailed; if (local_device_index != 0 or remote_device_index != 0) return VkError.ValidationFailed; return .{ .copy_src_bit = true, .copy_dst_bit = true, .generic_src_bit = true, .generic_dst_bit = true, }; } pub fn getDeviceGroupPresentCapabilitiesKHR(_: *Interface, capabilities: *vk.DeviceGroupPresentCapabilitiesKHR) VkError!void { capabilities.present_mask = @splat(0); capabilities.present_mask[0] = 1; capabilities.modes = .{ .local_bit_khr = true }; } pub fn getDeviceGroupSurfacePresentModesKHR(_: *Interface, _: *base.SurfaceKHR) VkError!vk.DeviceGroupPresentModeFlagsKHR { return .{ .local_bit_khr = true }; }