No more staging buffer; generic buffer doubles as staging

This commit is contained in:
2026-09-22 22:22:53 +02:00
parent 2fb4e38209
commit a6d2538352
5 changed files with 142 additions and 129 deletions

View File

@@ -21,7 +21,6 @@ const Iterator2 = math.Iterator2;
const Materials = @import("engine/Materials.zig");
const Player = @import("Player.zig");
const Skybox = @import("engine/Skybox.zig");
const StagingBuffer = @import("engine/StagingBuffer.zig");
const Swapchain = @import("engine/Swapchain.zig");
const Texture = @import("engine/Texture.zig");
const Textures = @import("engine/Textures.zig");
@@ -37,7 +36,8 @@ vertex_buffer: shaders.VertexBuffer,
index_buffer: shaders.IndexBuffer,
global_uniforms: shaders.GlobalUniformsBuffer,
global_uniforms_staging_buffer: StagingBuffer,
global_uniforms_staging_buffer: shaders.GlobalUniformsBuffer,
global_uniforms_staging_ptr: *shaders.GlobalUniforms,
global_uniforms_transfer_semaphores: []vk.Semaphore,
point_lights: shaders.PointLightBuffer,
directional_lights: shaders.DirectionalLightBuffer,
@@ -221,12 +221,17 @@ pub fn init() !Game {
});
errdefer global_uniforms.deinit();
var global_uniforms_staging_buffer = try StagingBuffer.init(.{
.capacity = @sizeOf(shaders.GlobalUniforms),
var global_uniforms_staging_buffer = try shaders.GlobalUniformsBuffer.init(.{
.usage = .staging,
.target_queue = .graphics,
.mappable = true,
.name = "GlobalUniforms Staging",
});
errdefer global_uniforms_staging_buffer.deinit();
const global_uniforms_staging_ptr = try global_uniforms_staging_buffer.map();
errdefer global_uniforms_staging_buffer.unmap();
const global_uniforms_transfer_semaphores = blk: {
var semaphores: std.ArrayList(vk.Semaphore) = try .initCapacity(allocator_general, swapchain.swapchain_images.len);
errdefer semaphores.deinit(allocator_general);
@@ -744,6 +749,7 @@ pub fn init() !Game {
.global_uniforms = global_uniforms,
.global_uniforms_staging_buffer = global_uniforms_staging_buffer,
.global_uniforms_staging_ptr = global_uniforms_staging_ptr,
.global_uniforms_transfer_semaphores = global_uniforms_transfer_semaphores,
.point_lights = point_lights,
.directional_lights = directional_lights,
@@ -776,6 +782,7 @@ pub fn deinit(self: *Game) void {
self.skybox.deinit();
self.global_uniforms.deinit();
self.global_uniforms_staging_buffer.unmap();
self.global_uniforms_staging_buffer.deinit();
self.point_lights.deinit();
self.directional_lights.deinit();
@@ -901,7 +908,7 @@ fn render(self: *Game) !void {
const ambient_light = vm.Vector3.init(0.01, 0.01, 0.01);
const global_uniforms_data: shaders.GlobalUniforms = .{
self.global_uniforms_staging_ptr.* = .{
.matrixWStoVS = matrix_ws_to_vs,
.matrixVStoCS = matrix_vs_to_cs,
.matrixSSPXtoCS = matrix_sspx_to_cs,
@@ -910,10 +917,7 @@ fn render(self: *Game) !void {
.directionalLightCount = directional_lights_data.len,
.ambientLight = ambient_light,
};
const staging_memory = try self.global_uniforms_staging_buffer.map();
@memcpy(staging_memory, std.mem.asBytes(&global_uniforms_data));
self.global_uniforms_staging_buffer.unmap();
try self.global_uniforms_staging_buffer.flush(0, 1);
try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1);
const global_uniforms_transfer_command_buffer = try CommandBuffer.init(.transfer);

View File

@@ -5,7 +5,6 @@ const vk = @import("vulkan");
const CommandBuffer = @import("CommandBuffer.zig");
const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig");
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
pub const Usage = enum {
@@ -14,6 +13,7 @@ pub const Usage = enum {
index,
vertex,
indirect,
staging,
pub fn asBufferUsageFlags(self: Usage) vk.BufferUsageFlags {
return switch (self) {
@@ -22,6 +22,7 @@ pub const Usage = enum {
.index => .{ .transfer_dst_bit = true, .index_buffer_bit = true },
.vertex => .{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
.indirect => .{ .transfer_dst_bit = true, .indirect_buffer_bit = true },
.staging => .{ .transfer_src_bit = true },
};
}
};
@@ -29,6 +30,7 @@ pub const Usage = enum {
pub const InitInfo = struct {
usage: Usage,
target_queue: TargetQueue,
mappable: bool = false,
capacity: u32 = 1,
name: ?[]const u8 = null,
};
@@ -39,6 +41,7 @@ pub fn GenericBuffer(comptime T: type) type {
buffer: vk.Buffer,
device_memory: vk.DeviceMemory,
mappable: bool,
target_queue: TargetQueue,
capacity: u32,
@@ -66,7 +69,10 @@ pub fn GenericBuffer(comptime T: type) type {
}
const memory_requirements = engine.getBufferMemoryRequirements(buffer);
const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
const device_memory = try engine.allocate(memory_requirements, .{
.device_local_bit = !init_info.mappable,
.host_visible_bit = init_info.mappable,
});
errdefer engine.freeMemory(device_memory);
if (init_info.name) |name| {
engine.setObjectName(device_memory, "DM {s} [{s}]", .{ name, @typeName(T) });
@@ -77,6 +83,7 @@ pub fn GenericBuffer(comptime T: type) type {
return .{
.buffer = buffer,
.device_memory = device_memory,
.mappable = init_info.mappable,
.target_queue = init_info.target_queue,
.capacity = init_info.capacity,
};
@@ -112,17 +119,30 @@ pub fn GenericBuffer(comptime T: type) type {
std.debug.assert(first_index + element_count <= self.capacity);
const write_size = element_count * element_size;
if (write_size == 0) return;
if (element_count == 0) return;
var staging_buffer: StagingBuffer = try .init(.{
if (self.mappable) {
const slice = try self.mapSliceAt(first_index, element_count);
defer self.unmap();
@memcpy(slice, values);
try self.flush(first_index, element_count);
} else {
var staging_buffer: GenericBuffer(T) = try .init(.{
.usage = .staging,
.target_queue = self.target_queue,
.capacity = write_size,
.mappable = true,
.capacity = element_count,
});
defer staging_buffer.deinit();
const staging_memory = try staging_buffer.map();
@memcpy(staging_memory, std.mem.sliceAsBytes(values));
staging_buffer.unmap();
{
const staging_slice = try staging_buffer.mapSlice();
defer staging_buffer.unmap();
@memcpy(staging_slice, values);
try staging_buffer.flush(0, element_count);
}
var command_buffer: CommandBuffer = try .init(.transfer);
defer command_buffer.deinit();
@@ -144,5 +164,66 @@ pub fn GenericBuffer(comptime T: type) type {
try command_buffer.submit(.{ .fence = fence });
try engine.waitForFence(fence);
}
}
pub fn map(self: Self) !*T {
std.debug.assert(self.mappable);
const slice = try self.mapSliceAt(0, 1);
return &slice[0];
}
pub fn mapAt(self: Self, index: u32) !*T {
std.debug.assert(self.mappable);
const slice = try self.mapSliceAt(index, 1);
return &slice[0];
}
pub fn mapSlice(self: Self) ![]T {
std.debug.assert(self.mappable);
return self.mapSliceAt(0, self.capacity);
}
pub fn mapSliceAt(self: Self, first_index: u32, count: u32) ![]T {
const engine = ctx.engine;
std.debug.assert(self.mappable);
std.debug.assert(first_index + count <= self.capacity);
const byte_offset = @as(u64, first_index) * @as(u64, element_size);
const byte_length = @as(u64, count) * @as(u64, element_size);
const mapped_memory = try engine.mapMemory(self.device_memory, byte_offset, byte_length, .{});
const slice = @as([*]T, @ptrCast(@alignCast(mapped_memory.ptr)))[0..count];
return slice;
}
pub fn flush(self: Self, first_index: u32, count: u32) !void {
const engine = ctx.engine;
std.debug.assert(self.mappable);
std.debug.assert(first_index + count <= self.capacity);
const byte_offset = @as(u64, first_index) * @as(u64, element_size);
const byte_length = @as(u64, count) * @as(u64, element_size);
try engine.device.flushMappedMemoryRanges(&.{
.{
.memory = self.device_memory,
.offset = byte_offset,
.size = byte_length,
},
});
}
pub fn unmap(self: Self) void {
const engine = ctx.engine;
std.debug.assert(self.mappable);
engine.unmapMemory(self.device_memory);
}
};
}

View File

@@ -10,7 +10,6 @@ const vm = @import("vecmath");
const CommandBuffer = @import("CommandBuffer.zig");
const Engine = @import("Engine.zig");
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
const StagingBuffer = @import("StagingBuffer.zig");
const Swapchain = @import("Swapchain.zig");
const Texture = @import("Texture.zig");
@@ -69,15 +68,21 @@ pub fn load(
// --- LOAD IMAGE INTO STAGING BUFFER --------------------------------------
var staging_buffer = try StagingBuffer.init(.{
.capacity = @intCast(img.width * img.height * @sizeOf(vm.ColorHdr)),
var staging_buffer = try GenericBuffer(vm.ColorHdr).init(.{
.usage = .staging,
.target_queue = .compute,
.mappable = true,
.capacity = img.width * img.height,
});
defer staging_buffer.deinit();
const staging_memory = try staging_buffer.map();
@memcpy(staging_memory, @as([*]const u8, @ptrCast(img.data)));
staging_buffer.unmap();
{
const staging_memory = try staging_buffer.mapSlice();
defer staging_buffer.unmap();
@memcpy(staging_memory, img.data);
try staging_buffer.flush(0, img.width * img.height);
}
// --- CREATE EQUIRECTANGULAR IMAGE ----------------------------------------

View File

@@ -1,83 +0,0 @@
const StagingBuffer = @This();
const std = @import("std");
const ctx = @import("../AppContext.zig");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
buffer: vk.Buffer,
device_memory: vk.DeviceMemory,
capacity: u32,
pub const InitInfo = struct {
target_queue: TargetQueue,
capacity: u32,
};
pub fn init(init_info: InitInfo) !StagingBuffer {
const engine = ctx.engine;
const target_queue_family = switch (init_info.target_queue) {
.graphics => engine.graphics_queue.allocation.family,
.compute => engine.compute_queue.allocation.family,
};
const transfer_queue_family = engine.transfer_queue.allocation.family;
const buffer = try engine.createBuffer(.{
.size = init_info.capacity,
.usage = .{
.transfer_src_bit = true,
},
.queue_family_indices = &.{ target_queue_family, transfer_queue_family },
});
errdefer engine.destroyBuffer(buffer);
const memory_requirements = engine.getBufferMemoryRequirements(buffer);
const device_memory = try engine.allocate(
memory_requirements,
.{
.host_visible_bit = true,
.host_coherent_bit = true,
},
);
errdefer engine.freeMemory(device_memory);
try engine.bindBufferMemory(buffer, device_memory, 0);
return .{
.buffer = buffer,
.device_memory = device_memory,
.capacity = init_info.capacity,
};
}
pub fn deinit(self: *StagingBuffer) void {
const engine = ctx.engine;
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
engine.freeMemory(self.device_memory);
engine.destroyBuffer(self.buffer);
self.* = undefined;
}
pub fn map(self: StagingBuffer) ![]u8 {
const mapped_memory = try self.mapPartial(0, self.capacity);
return mapped_memory;
}
pub fn mapPartial(self: StagingBuffer, offset: u32, len: u32) ![]u8 {
const engine = ctx.engine;
const mapped_memory = try engine.mapMemory(self.device_memory, offset, len, .{});
return mapped_memory;
}
pub fn unmap(self: StagingBuffer) void {
const engine = ctx.engine;
engine.unmapMemory(self.device_memory);
}

View File

@@ -7,7 +7,7 @@ const vk = @import("vulkan");
const CommandBuffer = @import("CommandBuffer.zig");
const DeviceAllocation = @import("DeviceAllocation.zig");
const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig");
const GenericBuffer = @import("GenericBuffer.zig").GenericBuffer;
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
pub const Usage = enum {
@@ -207,15 +207,21 @@ pub fn writeRaw(self: Texture, data: []const u8) !void {
std.debug.assert(data.len == byte_length);
std.debug.assert(self.usage != .depth);
var staging_buffer = try StagingBuffer.init(.{
.capacity = @intCast(byte_length),
var staging_buffer = try GenericBuffer(u8).init(.{
.usage = .staging,
.target_queue = self.target_queue,
.mappable = true,
.capacity = @intCast(byte_length),
});
defer staging_buffer.deinit();
const staging_memory = try staging_buffer.map();
{
const staging_memory = try staging_buffer.mapSlice();
defer staging_buffer.unmap();
@memcpy(staging_memory, data);
staging_buffer.unmap();
try staging_buffer.flush(0, byte_length);
}
// --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY -----------------