diff --git a/API_DESIGN.md b/API_DESIGN.md index fad5fcd..1f7b2d4 100644 --- a/API_DESIGN.md +++ b/API_DESIGN.md @@ -100,9 +100,9 @@ device.queue.submit([command_buffer], submit_options)! println('${physical_device.name()}: queue family ${device.queue.family_index}') ``` -`Queue` is borrowed from its parent `Device` and becomes invalid when that device is destroyed. `OwnedBuffer` exposes its raw buffer and memory handles, requested size, allocation size, and selected memory-type index. Its `destroy()` method always destroys the buffer before freeing its memory; callers must destroy every buffer before destroying the parent device. `PhysicalDevice.find_memory_type()` applies both the resource's allowed-memory-type bit mask and the complete required property mask. +`Queue` is borrowed from its parent `Device` and becomes invalid when that device is destroyed. `Device.queues` contains every requested queue in request and queue-index order, while `Device.queue` remains the selected legacy queue or the first multi-queue request for compatibility. Queue requests are grouped by distinct family, and each priority maps to the same zero-based queue index in that family. The convenience API creates ordinary queues with zero `VkDeviceQueueCreateFlags`; protected or otherwise flagged queue configurations remain available through the raw API. `OwnedBuffer` exposes its raw buffer and memory handles, requested size, allocation size, and selected memory-type index. Its `destroy()` method always destroys the buffer before freeing its memory; callers must destroy every buffer before destroying the parent device. `PhysicalDevice.find_memory_type()` applies both the resource's allowed-memory-type bit mask and the complete required property mask. -`CommandPool` belongs to its parent `Device` and is fixed to that device's selected queue-family index. `PrimaryCommandBuffer` retains the exact device and pool handles needed by `free()`, while its public raw `handle` remains available for recording and submission. `free()` is idempotent and clears that raw handle. Reset, begin, and end failures are returned as typed `VulkanError` values. Destroying a command pool implicitly frees and invalidates all command buffers still allocated from it; callers may either free buffers explicitly before pool destruction or rely on that Vulkan lifetime rule, but must never use or free a buffer after its pool is destroyed. Every command pool must be destroyed before its parent device. +`CommandPool` belongs to its parent `Device` and is fixed to one requested queue-family index. `new_command_pool()` uses `Device.queue`; `new_command_pool_for_queue()` accepts any queue borrowed from that device and rejects foreign queues. `PrimaryCommandBuffer` retains the exact device and pool handles needed by `free()`, while its public raw `handle` remains available for recording and submission. `free()` is idempotent and clears that raw handle. Reset, begin, and end failures are returned as typed `VulkanError` values. Destroying a command pool implicitly frees and invalidates all command buffers still allocated from it; callers may either free buffers explicitly before pool destruction or rely on that Vulkan lifetime rule, but must never use or free a buffer after its pool is destroyed. Every command pool must be destroyed before its parent device. `Fence` exposes status, timeout-aware waiting, and reset while preserving positive Vulkan statuses such as `VK_NOT_READY` and `VK_TIMEOUT`. `Fence` and `Semaphore` expose their raw handles for submission structures, clear those handles during idempotent destruction, and must be destroyed before their parent device. @@ -114,13 +114,13 @@ println('${physical_device.name()}: queue family ${device.queue.family_index}') `ImageLayoutTransition` keeps the synchronization-1 source/destination stage masks, access masks, old/new layouts, dependency flags, and aspect mask explicit. `PrimaryCommandBuffer.transition_image_layout()` records one image-only `vkCmdPipelineBarrier` over the owned image's single mip level and array layer. It does not infer synchronization, track layout state, or perform queue-family ownership transfers; use the raw API for broader ranges, ownership transfers, or synchronization-2 barriers. -`InstanceOptions` validates and owns instance layer/extension name pointers through creation. `DeviceOptions` configures one queue index and priority plus device extensions, core features, and an application-owned feature `pNext` chain. `PhysicalDevice.find_present_queue_family()` layers surface support over the existing queue-flag selection. Multiple queue requests, custom allocation callbacks, and concurrent-sharing resources remain in the raw layer. A future allocator-aware owning wrapper must retain the allocator used at creation so the same callbacks are supplied during destruction. +`InstanceOptions` validates and owns instance layer/extension name pointers through creation. `DeviceOptions` accepts either its compatible single-queue fields or `DeviceQueueRequest` values for multiple queues and families, plus device extensions, core features, and an application-owned feature `pNext` chain. `PhysicalDevice.find_present_queue_family()` layers surface support over the existing queue-flag selection. Custom allocation callbacks and concurrent-sharing resources remain in the raw layer. A future allocator-aware owning wrapper must retain the allocator used at creation so the same callbacks are supplied during destruction. ## Next slices 1. Instance extension and layer enumeration with owned V strings. (Implemented.) 2. Presentation-support selection layered onto the core queue-flag helper. (Implemented.) -3. Configurable queue requests, extension validation, and enabled features. (Single-queue configuration implemented; multiple queue requests remain.) +3. Configurable queue requests, extension validation, and enabled features. (Implemented, including multiple queues and families.) 4. Owned fences and binary semaphores with explicit parent ownership and destruction ordering. (Implemented.) 5. Owned 2D images with explicit parent ownership and destruction ordering. (Implemented.) 6. Checked primary command-buffer queue submission with explicit synchronization. (Implemented.) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb169c..19e83f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ This changelog tracks the semantic version of the V module in `v.mod`. Generated binding snapshots continue to use the Vulkan registry version stored in `VERSION`. +## Unreleased + +### Added + +- Multiple logical-device queue requests across distinct queue families, with + per-queue priorities, all borrowed queues exposed through `Device.queues`, + and queue-specific command-pool creation. + +### Fixed + +- Legacy nonzero queue indices now request every Vulkan queue through the + selected index before retrieving that queue. + ## 1.5.0 - 2026-09-08 ### Added diff --git a/README.md b/README.md index dbf7ae4..d725275 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,12 @@ Using GLFW and Dear ImGui [antono2/v_imgui_examples](https://github.com/antono2/ The generated module remains the complete low-level binding. The opt-in `antono2.vulkan.ergonomic` submodule adds typed errors, instance lifecycle -helpers, physical-device and queue-family discovery, and single-queue logical -device ownership. It also provides explicit memory-type selection and owned -buffer/device-memory allocation, owned command pools and primary command-buffer -lifecycle helpers, synchronization objects, checked queue submission, owned 2D -images and views, and explicit image-layout transition recording without modifying -generated files. +helpers, physical-device and queue-family discovery, and validated single- or +multi-queue logical-device ownership. It also provides explicit memory-type +selection and owned buffer/device-memory allocation, owned command pools and +primary command-buffer lifecycle helpers, synchronization objects, checked queue +submission, owned 2D images and views, and explicit image-layout transition +recording without modifying generated files. See [the ergonomic API design](API_DESIGN.md). Instance and device configuration can validate requested names before Vulkan is @@ -90,11 +90,18 @@ family := physical_device.find_queue_family(u32(vk.QueueFlagBits.graphics)) or { return error('no graphics queue') } device := physical_device.new_device_with_options(vke.DeviceOptions{ - queue_family: family + queue_requests: [vke.DeviceQueueRequest{ + queue_family: family + priorities: [f32(1.0)] + }] extensions: ['VK_KHR_swapchain'] })! ``` +Each priority requests the queue at the same zero-based index in its family. +Use one `DeviceQueueRequest` per distinct family; `device.queues` exposes the +created queues in request order and `device.queue` remains the first one. + The CI lifecycle smoke test runs the ergonomic path from instance creation through queue submission and cleanup under `VK_LAYER_KHRONOS_validation`. Run it locally with: diff --git a/ergonomic/configuration.v b/ergonomic/configuration.v index e7a7157..f84237f 100644 --- a/ergonomic/configuration.v +++ b/ergonomic/configuration.v @@ -53,31 +53,109 @@ pub fn new_instance_with_options(options InstanceOptions) !Instance { return new_instance(&create_info) } -// DeviceOptions configures the single queue exposed by Device together with -// device extensions, core features, and an optional feature pNext chain. +// DeviceQueueRequest requests consecutive queues from one family. Each +// priority maps to the queue at the same zero-based index in that family. +pub struct DeviceQueueRequest { +pub: + queue_family QueueFamily + priorities []f32 = [f32(1.0)] +} + +// DeviceOptions configures logical-device queues together with device +// extensions, core features, and an optional feature pNext chain. +// +// queue_family, queue_index, and queue_priority preserve the original +// single-queue API. Set queue_requests to request queues from one or more +// families; it cannot be combined with those legacy fields. The first queue +// in queue_requests becomes Device.queue. pub struct DeviceOptions { pub: queue_family QueueFamily queue_index u32 queue_priority f32 = 1.0 + queue_requests []DeviceQueueRequest extensions []string enabled_features &vk.PhysicalDeviceFeatures = unsafe { nil } p_next voidptr = unsafe { nil } } -// new_device_with_options validates the queue request and device extensions, -// then creates a logical device while retaining all temporary pointer arrays -// through vkCreateDevice. -pub fn (physical_device PhysicalDevice) new_device_with_options(options DeviceOptions) !Device { - if options.queue_family.properties.queueCount == 0 { - return error('queue family ${options.queue_family.index} has no queues') +struct DeviceQueuePlan { + requests []DeviceQueueRequest + primary_family_index u32 + primary_queue_index u32 +} + +fn validate_device_queue_request(request DeviceQueueRequest) ! { + if request.queue_family.properties.queueCount == 0 { + return error('queue family ${request.queue_family.index} has no queues') + } + if request.priorities.len == 0 { + return error('queue family ${request.queue_family.index} must request at least one queue') + } + if request.priorities.len > int(request.queue_family.properties.queueCount) { + return error('requested ${request.priorities.len} queues from family ${request.queue_family.index}, which exposes ${request.queue_family.properties.queueCount}') + } + for queue_index, priority in request.priorities { + // Express this as an accepted range so NaN is rejected as well. + if !(priority >= 0.0 && priority <= 1.0) { + return error('queue priority for family ${request.queue_family.index} index ${queue_index} must be between 0.0 and 1.0') + } + } +} + +fn device_queue_plan(options DeviceOptions) !DeviceQueuePlan { + if options.queue_requests.len == 0 { + if options.queue_family.properties.queueCount == 0 { + return error('queue family ${options.queue_family.index} has no queues') + } + if options.queue_index >= options.queue_family.properties.queueCount { + return error('queue index ${options.queue_index} is outside queue family ${options.queue_family.index}') + } + if !(options.queue_priority >= 0.0 && options.queue_priority <= 1.0) { + return error('queue priority must be between 0.0 and 1.0') + } + + // Vulkan creates queues consecutively from index zero. Request every + // index through the selected legacy queue so vkGetDeviceQueue never + // targets a queue which was not created. + priorities := []f32{len: int(options.queue_index) + 1, init: options.queue_priority} + return DeviceQueuePlan{ + requests: [ + DeviceQueueRequest{ + queue_family: options.queue_family + priorities: priorities + }, + ] + primary_family_index: options.queue_family.index + primary_queue_index: options.queue_index + } + } + + if options.queue_family.properties.queueCount != 0 || options.queue_index != 0 + || options.queue_priority != 1.0 { + return error('queue_requests cannot be combined with queue_family, queue_index, or queue_priority') } - if options.queue_index >= options.queue_family.properties.queueCount { - return error('queue index ${options.queue_index} is outside queue family ${options.queue_family.index}') + + mut seen_families := map[u32]bool{} + for request in options.queue_requests { + validate_device_queue_request(request)! + if seen_families[request.queue_family.index] { + return error('queue family ${request.queue_family.index} is requested more than once') + } + seen_families[request.queue_family.index] = true } - if options.queue_priority < 0.0 || options.queue_priority > 1.0 { - return error('queue priority must be between 0.0 and 1.0') + return DeviceQueuePlan{ + requests: options.queue_requests.clone() + primary_family_index: options.queue_requests[0].queue_family.index + primary_queue_index: 0 } +} + +// new_device_with_options validates queue requests and device extensions, +// then creates a logical device while retaining all temporary priority and +// pointer arrays through vkCreateDevice. +pub fn (physical_device PhysicalDevice) new_device_with_options(options DeviceOptions) !Device { + queue_plan := device_queue_plan(options)! available_extensions := extension_names(physical_device.extensions()!) validate_requested_names('device extensions', options.extensions, available_extensions)! @@ -85,16 +163,22 @@ pub fn (physical_device PhysicalDevice) new_device_with_options(options DeviceOp for extension in options.extensions { extension_pointers << extension.str } - priority := options.queue_priority - queue_info := vk.DeviceQueueCreateInfo{ - queueFamilyIndex: options.queue_family.index - queueCount: 1 - pQueuePriorities: &priority + mut priority_groups := [][]f32{cap: queue_plan.requests.len} + for request in queue_plan.requests { + priority_groups << request.priorities.clone() + } + mut queue_infos := []vk.DeviceQueueCreateInfo{cap: queue_plan.requests.len} + for request_index, request in queue_plan.requests { + queue_infos << vk.DeviceQueueCreateInfo{ + queueFamilyIndex: request.queue_family.index + queueCount: u32(priority_groups[request_index].len) + pQueuePriorities: priority_groups[request_index].data + } } create_info := vk.DeviceCreateInfo{ pNext: options.p_next - queueCreateInfoCount: 1 - pQueueCreateInfos: &queue_info + queueCreateInfoCount: u32(queue_infos.len) + pQueueCreateInfos: queue_infos.data enabledExtensionCount: u32(extension_pointers.len) ppEnabledExtensionNames: extension_pointers.data pEnabledFeatures: options.enabled_features @@ -103,16 +187,30 @@ pub fn (physical_device PhysicalDevice) new_device_with_options(options DeviceOp require_success(vk.create_device(physical_device.handle, &create_info, unsafe { nil }, &handle), 'vkCreateDevice')! vk.load_device_commands(handle) - mut queue_handle := vk.Queue(unsafe { nil }) - vk.get_device_queue(handle, options.queue_family.index, options.queue_index, &queue_handle) + mut queues := []Queue{} + mut primary_queue := Queue{} + for request in queue_plan.requests { + for queue_index in 0 .. request.priorities.len { + mut queue_handle := vk.Queue(unsafe { nil }) + vk.get_device_queue(handle, request.queue_family.index, u32(queue_index), &queue_handle) + queue := Queue{ + device: handle + handle: queue_handle + family_index: request.queue_family.index + index: u32(queue_index) + } + queues << queue + if queue.family_index == queue_plan.primary_family_index + && queue.index == queue_plan.primary_queue_index { + primary_queue = queue + } + } + } return Device{ physical_device: physical_device handle: handle - queue: Queue{ - handle: queue_handle - family_index: options.queue_family.index - index: options.queue_index - } + queue: primary_queue + queues: queues } } diff --git a/ergonomic/configuration_test.v b/ergonomic/configuration_test.v index aaefd47..8f1ee09 100644 --- a/ergonomic/configuration_test.v +++ b/ergonomic/configuration_test.v @@ -71,3 +71,167 @@ fn test_device_options_reject_invalid_priority_before_vulkan_calls() { } assert false } + +fn test_legacy_queue_plan_requests_every_queue_through_selected_index() ! { + family := QueueFamily{ + index: 3 + properties: vk.QueueFamilyProperties{ + queueFlags: u32(vk.QueueFlagBits.graphics) + queueCount: 3 + } + } + plan := device_queue_plan(DeviceOptions{ + queue_family: family + queue_index: 2 + queue_priority: 0.75 + })! + assert plan.requests.len == 1 + assert plan.requests[0].queue_family.index == 3 + assert plan.requests[0].priorities == [f32(0.75), 0.75, 0.75] + assert plan.primary_family_index == 3 + assert plan.primary_queue_index == 2 +} + +fn test_multi_queue_plan_preserves_family_and_priority_order() ! { + graphics := QueueFamily{ + index: 1 + properties: vk.QueueFamilyProperties{ + queueFlags: u32(vk.QueueFlagBits.graphics) + queueCount: 2 + } + } + transfer := QueueFamily{ + index: 4 + properties: vk.QueueFamilyProperties{ + queueFlags: u32(vk.QueueFlagBits.transfer) + queueCount: 1 + } + } + plan := device_queue_plan(DeviceOptions{ + queue_requests: [ + DeviceQueueRequest{ + queue_family: graphics + priorities: [f32(1.0), 0.5] + }, + DeviceQueueRequest{ + queue_family: transfer + priorities: [f32(0.25)] + }, + ] + })! + assert plan.requests.len == 2 + assert plan.requests[0].priorities == [f32(1.0), 0.5] + assert plan.requests[1].queue_family.index == 4 + assert plan.primary_family_index == 1 + assert plan.primary_queue_index == 0 +} + +fn test_multi_queue_options_reject_empty_priority_list() { + physical_device := PhysicalDevice{} + family := QueueFamily{ + index: 2 + properties: vk.QueueFamilyProperties{ + queueCount: 1 + } + } + physical_device.new_device_with_options(DeviceOptions{ + queue_requests: [ + DeviceQueueRequest{ + queue_family: family + priorities: [] + }, + ] + }) or { + assert err.msg() == 'queue family 2 must request at least one queue' + return + } + assert false +} + +fn test_multi_queue_options_reject_excess_queue_count() { + physical_device := PhysicalDevice{} + family := QueueFamily{ + index: 2 + properties: vk.QueueFamilyProperties{ + queueCount: 1 + } + } + physical_device.new_device_with_options(DeviceOptions{ + queue_requests: [ + DeviceQueueRequest{ + queue_family: family + priorities: [f32(1.0), 0.5] + }, + ] + }) or { + assert err.msg() == 'requested 2 queues from family 2, which exposes 1' + return + } + assert false +} + +fn test_multi_queue_options_reject_invalid_priority() { + physical_device := PhysicalDevice{} + family := QueueFamily{ + index: 2 + properties: vk.QueueFamilyProperties{ + queueCount: 1 + } + } + physical_device.new_device_with_options(DeviceOptions{ + queue_requests: [ + DeviceQueueRequest{ + queue_family: family + priorities: [f32(-0.25)] + }, + ] + }) or { + assert err.msg() == 'queue priority for family 2 index 0 must be between 0.0 and 1.0' + return + } + assert false +} + +fn test_multi_queue_options_reject_duplicate_families() { + physical_device := PhysicalDevice{} + family := QueueFamily{ + index: 2 + properties: vk.QueueFamilyProperties{ + queueCount: 1 + } + } + physical_device.new_device_with_options(DeviceOptions{ + queue_requests: [ + DeviceQueueRequest{ + queue_family: family + }, + DeviceQueueRequest{ + queue_family: family + }, + ] + }) or { + assert err.msg() == 'queue family 2 is requested more than once' + return + } + assert false +} + +fn test_multi_queue_options_reject_legacy_queue_fields() { + physical_device := PhysicalDevice{} + family := QueueFamily{ + index: 2 + properties: vk.QueueFamilyProperties{ + queueCount: 1 + } + } + physical_device.new_device_with_options(DeviceOptions{ + queue_family: family + queue_requests: [DeviceQueueRequest{ + queue_family: family + }] + }) or { + assert err.msg() == 'queue_requests cannot be combined with queue_family, queue_index, or queue_priority' + return + } + assert false +} diff --git a/ergonomic/ergonomic.v b/ergonomic/ergonomic.v index 190ba90..ccd5707 100644 --- a/ergonomic/ergonomic.v +++ b/ergonomic/ergonomic.v @@ -165,19 +165,22 @@ pub fn (device PhysicalDevice) find_queue_family(required_flags vk.QueueFlags) ? // Queue is a borrowed queue handle owned by its parent Device. pub struct Queue { + device vk.Device pub: handle vk.Queue family_index u32 index u32 } -// Device owns a logical VkDevice and exposes its single requested queue. It +// Device owns a logical VkDevice and exposes all requested queues. queue is +// the selected legacy queue or the first queue in a multi-queue request. It // does not destroy itself implicitly; call destroy exactly once. pub struct Device { physical_device PhysicalDevice pub: handle vk.Device queue Queue + queues []Queue } // new_device creates a logical device with one queue at priority 1.0 from the @@ -190,8 +193,8 @@ pub fn (physical_device PhysicalDevice) new_device(queue_family QueueFamily) !De }) } -// destroy destroys a logical device created by new_device. Its queue handle -// becomes invalid at the same time. +// destroy destroys a logical device created by new_device. All of its queue +// handles become invalid at the same time. pub fn (device Device) destroy() { vk.destroy_device(device.handle, unsafe { nil }) } @@ -209,16 +212,25 @@ pub: // new_command_pool creates a command pool for the Device's queue family with // Vulkan's default allocator. pub fn (device Device) new_command_pool(flags vk.CommandPoolCreateFlags) !CommandPool { + return device.new_command_pool_for_queue(device.queue, flags) +} + +// new_command_pool_for_queue creates a command pool for one of this Device's +// requested queues. Passing a queue borrowed from another device is rejected. +pub fn (device Device) new_command_pool_for_queue(queue Queue, flags vk.CommandPoolCreateFlags) !CommandPool { + if queue.device != device.handle { + return error('queue does not belong to this device') + } create_info := vk.CommandPoolCreateInfo{ flags: flags - queueFamilyIndex: device.queue.family_index + queueFamilyIndex: queue.family_index } mut handle := vk.CommandPool(unsafe { nil }) require_success(vk.create_command_pool(device.handle, &create_info, unsafe { nil }, &handle), 'vkCreateCommandPool')! return CommandPool{ device: device.handle handle: handle - queue_family_index: device.queue.family_index + queue_family_index: queue.family_index flags: flags } } diff --git a/ergonomic/ergonomic_test.v b/ergonomic/ergonomic_test.v index dc980ce..012eaa4 100644 --- a/ergonomic/ergonomic_test.v +++ b/ergonomic/ergonomic_test.v @@ -115,6 +115,20 @@ fn test_command_pool_and_primary_buffer_expose_raw_handles_and_ownership_metadat assert buffer.command_pool == pool.handle } +fn test_command_pool_rejects_queue_from_another_device_before_vulkan_call() { + device := Device{ + handle: vk.Device(unsafe { voidptr(1) }) + } + foreign_queue := Queue{ + device: vk.Device(unsafe { voidptr(2) }) + } + device.new_command_pool_for_queue(foreign_queue, 0) or { + assert err.msg() == 'queue does not belong to this device' + return + } + assert false +} + fn test_allocate_primary_rejects_zero_count_before_calling_vulkan() { pool := CommandPool{ device: vk.Device(unsafe { nil }) diff --git a/examples/ergonomic_lifecycle/main.v b/examples/ergonomic_lifecycle/main.v index 9c83f50..533030d 100644 --- a/examples/ergonomic_lifecycle/main.v +++ b/examples/ergonomic_lifecycle/main.v @@ -22,12 +22,33 @@ fn main() { queue_family := physical_device.find_queue_family(required_queue_flags) or { panic('no graphics/compute queue family was found') } + if queue_family.properties.queueCount >= 2 { + legacy_device := physical_device.new_device_with_options(vke.DeviceOptions{ + queue_family: queue_family + queue_index: 1 + }) or { panic(err) } + if legacy_device.queue.index != 1 || legacy_device.queues.len != 2 { + panic('legacy nonzero queue selection did not create the required queue range') + } + legacy_device.destroy() + } + requested_queue_count := if queue_family.properties.queueCount >= 2 { 2 } else { 1 } device := physical_device.new_device_with_options(vke.DeviceOptions{ - queue_family: queue_family + queue_requests: [ + vke.DeviceQueueRequest{ + queue_family: queue_family + priorities: []f32{len: requested_queue_count, init: 1.0} + }, + ] }) or { panic(err) } defer { device.destroy() } + if device.queues.len != requested_queue_count || device.queue.family_index != queue_family.index + || device.queue.index != 0 { + panic('multi-queue creation returned unexpected queue metadata') + } + work_queue := device.queues[device.queues.len - 1] memory_properties := u32(vk.MemoryPropertyFlagBits.device_local) buffer_usage := u32(vk.BufferUsageFlagBits.vertex_buffer) | u32(vk.BufferUsageFlagBits.transfer_dst) @@ -37,7 +58,9 @@ fn main() { } image_usage := u32(vk.ImageUsageFlagBits.sampled) | u32(vk.ImageUsageFlagBits.transfer_dst) - image := device.new_image_2d(64, 64, .r8g8b8a8_unorm, .optimal, image_usage, memory_properties) or { panic(err) } + image := device.new_image_2d(64, 64, .r8g8b8a8_unorm, .optimal, image_usage, memory_properties) or { + panic(err) + } defer { image.destroy() } @@ -47,7 +70,7 @@ fn main() { } pool_flags := u32(vk.CommandPoolCreateFlagBits.reset_command_buffer) - pool := device.new_command_pool(pool_flags) or { panic(err) } + pool := device.new_command_pool_for_queue(work_queue, pool_flags) or { panic(err) } defer { pool.destroy() } @@ -75,12 +98,12 @@ fn main() { defer { semaphore.destroy() } - device.queue.submit([command_buffer], vke.SubmitOptions{ + work_queue.submit([command_buffer], vke.SubmitOptions{ fence: fence }) or { panic(err) } if fence.wait(5_000_000_000) or { panic(err) } != .success { panic('queue submission did not complete before the smoke-test timeout') } - println('Vulkan ergonomic lifecycle passed on ${physical_device.name()}') + println('Vulkan ergonomic lifecycle passed on ${physical_device.name()} with ${device.queues.len} queue(s)') }