renderer/vulkan: VkCommandPool wrapper + one-shot helper
Adds `vulkan/CommandPool.zig` — the missing piece between `Texture.zig` having an image handle and actually being able to upload pixels to it. Provides the `init` / `deinit` lifecycle plus a `beginOneShot` → `OneShot.endAndSubmit` helper that runs a caller-recorded command buffer to completion. Pool flags: `TRANSIENT_BIT | RESET_COMMAND_BUFFER_BIT`. The transient hint matches our usage pattern (every CB allocated here is short-lived); the reset bit lets us free individual buffers without dropping the whole pool. One-shot semantics: alloc → begin → caller records → end → submit → `vkQueueWaitIdle` → free. The wait is acceptable here because the only consumer for now is atlas / texture upload, which is rare and naturally synchronous (the renderer wants the texture populated before sampling it the next frame). Per-frame command submission will land separately with fence-based pacing — never `queueWaitIdle`. Dispatch additions: 10 new entries for the full one-shot path — `vkCreateCommandPool`, `vkDestroyCommandPool`, `vkAllocateCommandBuffers`, `vkFreeCommandBuffers`, `vkBeginCommandBuffer`, `vkEndCommandBuffer`, `vkQueueSubmit`, `vkQueueWaitIdle`, `vkCmdPipelineBarrier`, `vkCmdCopyBufferToImage`. The last two are loaded here (rather than in the upcoming texture- upload commit) because they're command-buffer-recording functions and naturally belong with the rest of the command-buffer surface. Verification: temp-switch compile-check; only the expected downstream `DerivedConfig` error surfaced. Reverted. OpenGL build still silent / clean. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
a1a6d45c79
commit
fafd928a80
|
|
@ -66,6 +66,7 @@
|
|||
pub const Device = @import("vulkan/Device.zig");
|
||||
pub const Sampler = @import("vulkan/Sampler.zig");
|
||||
pub const Texture = @import("vulkan/Texture.zig");
|
||||
pub const CommandPool = @import("vulkan/CommandPool.zig");
|
||||
|
||||
const bufferpkg = @import("vulkan/buffer.zig");
|
||||
pub const Buffer = bufferpkg.Buffer;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
//! Wrapper for `VkCommandPool` with a one-shot command-buffer helper.
|
||||
//!
|
||||
//! Initially used by `vulkan/Texture.zig` for staging-buffer uploads:
|
||||
//! allocate a transient command buffer, record an upload + layout
|
||||
//! barriers, submit, wait for completion, free.
|
||||
//!
|
||||
//! Eventually the renderer will grow a separate per-frame command
|
||||
//! pool for the main draw stream; this pool stays around for
|
||||
//! infrequent operations like atlas uploads where blocking the
|
||||
//! caller is fine. The choice keeps the API small and avoids the
|
||||
//! complication of multi-frame fence tracking for resources that
|
||||
//! will outlive the upload.
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan").c;
|
||||
|
||||
const Device = @import("Device.zig");
|
||||
|
||||
const log = std.log.scoped(.vulkan);
|
||||
|
||||
pub const Error = error{
|
||||
/// A `vkCreateCommandPool` / `vkAllocateCommandBuffers` /
|
||||
/// `vkBeginCommandBuffer` / `vkEndCommandBuffer` / `vkQueueSubmit`
|
||||
/// returned a non-success status. Logged with the raw `VkResult`.
|
||||
VulkanFailed,
|
||||
};
|
||||
|
||||
device: *const Device,
|
||||
pool: vk.VkCommandPool,
|
||||
|
||||
/// Create a command pool on the device's graphics queue family. The
|
||||
/// pool is created with `TRANSIENT_BIT | RESET_COMMAND_BUFFER_BIT`
|
||||
/// because every command buffer we allocate here is short-lived and
|
||||
/// freed (or reset) immediately after submit.
|
||||
pub fn init(device: *const Device) Error!Self {
|
||||
const info: vk.VkCommandPoolCreateInfo = .{
|
||||
.sType = vk.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.pNext = null,
|
||||
.flags = vk.VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
|
||||
vk.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = device.queue_family_index,
|
||||
};
|
||||
var pool: vk.VkCommandPool = undefined;
|
||||
const r = device.dispatch.createCommandPool(device.device, &info, null, &pool);
|
||||
if (r != vk.VK_SUCCESS) {
|
||||
log.err("vkCreateCommandPool failed: result={}", .{r});
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
return .{ .device = device, .pool = pool };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.device.dispatch.destroyCommandPool(self.device.device, self.pool, null);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// A one-shot recording session. Yielded from `beginOneShot`, drives
|
||||
/// `endAndSubmit` when the caller is done recording.
|
||||
pub const OneShot = struct {
|
||||
pool: *Self,
|
||||
cb: vk.VkCommandBuffer,
|
||||
|
||||
/// Record any commands directly via `cb` and the device dispatch
|
||||
/// table (e.g. `pool.device.dispatch.cmdPipelineBarrier(cb, …)`).
|
||||
/// Then call `endAndSubmit`. The command buffer is freed by the
|
||||
/// time this returns.
|
||||
pub fn endAndSubmit(self: OneShot) Error!void {
|
||||
const dev = self.pool.device;
|
||||
|
||||
{
|
||||
const r = dev.dispatch.endCommandBuffer(self.cb);
|
||||
if (r != vk.VK_SUCCESS) {
|
||||
log.err("vkEndCommandBuffer failed: result={}", .{r});
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
}
|
||||
|
||||
const submit_info: vk.VkSubmitInfo = .{
|
||||
.sType = vk.VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.pNext = null,
|
||||
.waitSemaphoreCount = 0,
|
||||
.pWaitSemaphores = null,
|
||||
.pWaitDstStageMask = null,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &self.cb,
|
||||
.signalSemaphoreCount = 0,
|
||||
.pSignalSemaphores = null,
|
||||
};
|
||||
{
|
||||
const r = dev.dispatch.queueSubmit(dev.queue, 1, &submit_info, null);
|
||||
if (r != vk.VK_SUCCESS) {
|
||||
log.err("vkQueueSubmit failed: result={}", .{r});
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
}
|
||||
|
||||
// Block until the submit completes. Acceptable for one-shot
|
||||
// uploads (atlas resizes are rare and the caller is willing
|
||||
// to stall). Per-frame command submission will use fences
|
||||
// and never queueWaitIdle.
|
||||
{
|
||||
const r = dev.dispatch.queueWaitIdle(dev.queue);
|
||||
if (r != vk.VK_SUCCESS) {
|
||||
log.err("vkQueueWaitIdle failed: result={}", .{r});
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
}
|
||||
|
||||
// Free the command buffer. The pool itself stays around so
|
||||
// back-to-back uploads can reuse it without re-allocating
|
||||
// VkCommandPool.
|
||||
const cb_local = self.cb;
|
||||
dev.dispatch.freeCommandBuffers(dev.device, self.pool.pool, 1, &cb_local);
|
||||
}
|
||||
};
|
||||
|
||||
/// Allocate + begin a transient command buffer for a one-shot
|
||||
/// upload. Pair with `OneShot.endAndSubmit`.
|
||||
pub fn beginOneShot(self: *Self) Error!OneShot {
|
||||
const dev = self.device;
|
||||
|
||||
const alloc_info: vk.VkCommandBufferAllocateInfo = .{
|
||||
.sType = vk.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.pNext = null,
|
||||
.commandPool = self.pool,
|
||||
.level = vk.VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
var cb: vk.VkCommandBuffer = undefined;
|
||||
{
|
||||
const r = dev.dispatch.allocateCommandBuffers(dev.device, &alloc_info, &cb);
|
||||
if (r != vk.VK_SUCCESS) {
|
||||
log.err("vkAllocateCommandBuffers failed: result={}", .{r});
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
}
|
||||
errdefer dev.dispatch.freeCommandBuffers(dev.device, self.pool, 1, &cb);
|
||||
|
||||
const begin_info: vk.VkCommandBufferBeginInfo = .{
|
||||
.sType = vk.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.pNext = null,
|
||||
.flags = vk.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
.pInheritanceInfo = null,
|
||||
};
|
||||
{
|
||||
const r = dev.dispatch.beginCommandBuffer(cb, &begin_info);
|
||||
if (r != vk.VK_SUCCESS) {
|
||||
log.err("vkBeginCommandBuffer failed: result={}", .{r});
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .pool = self, .cb = cb };
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
|
@ -109,6 +109,20 @@ pub const Dispatch = struct {
|
|||
bindBufferMemory: std.meta.Child(vk.PFN_vkBindBufferMemory),
|
||||
mapMemory: std.meta.Child(vk.PFN_vkMapMemory),
|
||||
unmapMemory: std.meta.Child(vk.PFN_vkUnmapMemory),
|
||||
|
||||
// Command pool / buffer + queue submit + recording —
|
||||
// used by `vulkan/CommandPool.zig` and (later) per-frame command
|
||||
// recording in `vulkan/Frame.zig`.
|
||||
createCommandPool: std.meta.Child(vk.PFN_vkCreateCommandPool),
|
||||
destroyCommandPool: std.meta.Child(vk.PFN_vkDestroyCommandPool),
|
||||
allocateCommandBuffers: std.meta.Child(vk.PFN_vkAllocateCommandBuffers),
|
||||
freeCommandBuffers: std.meta.Child(vk.PFN_vkFreeCommandBuffers),
|
||||
beginCommandBuffer: std.meta.Child(vk.PFN_vkBeginCommandBuffer),
|
||||
endCommandBuffer: std.meta.Child(vk.PFN_vkEndCommandBuffer),
|
||||
queueSubmit: std.meta.Child(vk.PFN_vkQueueSubmit),
|
||||
queueWaitIdle: std.meta.Child(vk.PFN_vkQueueWaitIdle),
|
||||
cmdPipelineBarrier: std.meta.Child(vk.PFN_vkCmdPipelineBarrier),
|
||||
cmdCopyBufferToImage: std.meta.Child(vk.PFN_vkCmdCopyBufferToImage),
|
||||
};
|
||||
|
||||
// ---- fields ---------------------------------------------------------
|
||||
|
|
@ -301,6 +315,26 @@ pub fn init(
|
|||
try dl.load(vk.PFN_vkMapMemory, "vkMapMemory");
|
||||
const unmap_memory =
|
||||
try dl.load(vk.PFN_vkUnmapMemory, "vkUnmapMemory");
|
||||
const create_command_pool =
|
||||
try dl.load(vk.PFN_vkCreateCommandPool, "vkCreateCommandPool");
|
||||
const destroy_command_pool =
|
||||
try dl.load(vk.PFN_vkDestroyCommandPool, "vkDestroyCommandPool");
|
||||
const allocate_command_buffers =
|
||||
try dl.load(vk.PFN_vkAllocateCommandBuffers, "vkAllocateCommandBuffers");
|
||||
const free_command_buffers =
|
||||
try dl.load(vk.PFN_vkFreeCommandBuffers, "vkFreeCommandBuffers");
|
||||
const begin_command_buffer =
|
||||
try dl.load(vk.PFN_vkBeginCommandBuffer, "vkBeginCommandBuffer");
|
||||
const end_command_buffer =
|
||||
try dl.load(vk.PFN_vkEndCommandBuffer, "vkEndCommandBuffer");
|
||||
const queue_submit =
|
||||
try dl.load(vk.PFN_vkQueueSubmit, "vkQueueSubmit");
|
||||
const queue_wait_idle =
|
||||
try dl.load(vk.PFN_vkQueueWaitIdle, "vkQueueWaitIdle");
|
||||
const cmd_pipeline_barrier =
|
||||
try dl.load(vk.PFN_vkCmdPipelineBarrier, "vkCmdPipelineBarrier");
|
||||
const cmd_copy_buffer_to_image =
|
||||
try dl.load(vk.PFN_vkCmdCopyBufferToImage, "vkCmdCopyBufferToImage");
|
||||
|
||||
return .{
|
||||
.platform = platform,
|
||||
|
|
@ -333,6 +367,16 @@ pub fn init(
|
|||
.bindBufferMemory = bind_buffer_memory,
|
||||
.mapMemory = map_memory,
|
||||
.unmapMemory = unmap_memory,
|
||||
.createCommandPool = create_command_pool,
|
||||
.destroyCommandPool = destroy_command_pool,
|
||||
.allocateCommandBuffers = allocate_command_buffers,
|
||||
.freeCommandBuffers = free_command_buffers,
|
||||
.beginCommandBuffer = begin_command_buffer,
|
||||
.endCommandBuffer = end_command_buffer,
|
||||
.queueSubmit = queue_submit,
|
||||
.queueWaitIdle = queue_wait_idle,
|
||||
.cmdPipelineBarrier = cmd_pipeline_barrier,
|
||||
.cmdCopyBufferToImage = cmd_copy_buffer_to_image,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue