Refactor literally everything
This commit is contained in:
@@ -281,7 +281,17 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine {
|
||||
try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name);
|
||||
}
|
||||
|
||||
const enabled_features_vulkan10: vk.PhysicalDeviceFeatures = .{
|
||||
.shader_int_16 = .true,
|
||||
};
|
||||
|
||||
var enabled_features_vulkan11: vk.PhysicalDeviceVulkan11Features = .{
|
||||
.storage_buffer_16_bit_access = .true,
|
||||
.uniform_and_storage_buffer_16_bit_access = .true,
|
||||
};
|
||||
|
||||
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
|
||||
.p_next = &enabled_features_vulkan11,
|
||||
.descriptor_binding_partially_bound = .true,
|
||||
.descriptor_binding_variable_descriptor_count = .true,
|
||||
.runtime_descriptor_array = .true,
|
||||
@@ -294,6 +304,7 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine {
|
||||
.p_queue_create_infos = queue_create_info.items.ptr,
|
||||
.enabled_extension_count = @intCast(enabled_extensions.items.len),
|
||||
.pp_enabled_extension_names = enabled_extensions.items.ptr,
|
||||
.p_enabled_features = &enabled_features_vulkan10,
|
||||
}, &vk_allocator.interface);
|
||||
};
|
||||
|
||||
@@ -561,3 +572,541 @@ fn findPresentationQueueFamily(queue_families_properties: []const vk.QueueFamily
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- VULKAN WRAPPERS ---------------------------------------------------------
|
||||
|
||||
pub const BufferCreateInfo = struct {
|
||||
flags: vk.BufferCreateFlags = .{},
|
||||
size: vk.DeviceSize,
|
||||
usage: vk.BufferUsageFlags,
|
||||
queue_family_indices: []const u32 = &.{},
|
||||
};
|
||||
|
||||
pub const DescriptorPoolCreateInfo = struct {
|
||||
flags: vk.DescriptorPoolCreateFlags = .{},
|
||||
max_sets: u32,
|
||||
pool_sizes: []const vk.DescriptorPoolSize = &.{},
|
||||
};
|
||||
|
||||
pub const DescriptorSetAllocateInfo = struct {
|
||||
descriptor_pool: vk.DescriptorPool,
|
||||
set_layouts: []const vk.DescriptorSetLayout,
|
||||
variable_descriptor_counts: []const u32 = &.{},
|
||||
};
|
||||
|
||||
pub const DescriptorSetLayoutBinding = struct {
|
||||
binding: u32,
|
||||
descriptor_type: vk.DescriptorType,
|
||||
descriptor_count: u32,
|
||||
stage_flags: vk.ShaderStageFlags,
|
||||
immutable_samplers: []const vk.Sampler = &.{},
|
||||
flags: vk.DescriptorBindingFlags = .{},
|
||||
};
|
||||
|
||||
pub const DescriptorSetLayoutCreateInfo = struct {
|
||||
flags: vk.DescriptorSetLayoutCreateFlags = .{},
|
||||
bindings: []const DescriptorSetLayoutBinding = &.{},
|
||||
};
|
||||
|
||||
pub const DescriptorSetsUpdateInfo = struct {
|
||||
writes: []const WriteDescriptorSet = &.{},
|
||||
copies: []const vk.CopyDescriptorSet = &.{},
|
||||
};
|
||||
|
||||
pub const FramebufferCreateInfo = struct {
|
||||
flags: vk.FramebufferCreateFlags = .{},
|
||||
render_pass: vk.RenderPass,
|
||||
attachments: []const vk.ImageView = &.{},
|
||||
width: u32,
|
||||
height: u32,
|
||||
layers: u32,
|
||||
};
|
||||
|
||||
pub const GraphicsPipelineCreateInfo = struct {
|
||||
flags: vk.PipelineCreateFlags = .{},
|
||||
stages: []const PipelineShaderStageCreateInfo = &.{},
|
||||
vertex_input_state: ?PipelineVertexInputStateCreateInfo = null,
|
||||
input_assembly_state: ?vk.PipelineInputAssemblyStateCreateInfo = null,
|
||||
tessellation_state: ?vk.PipelineTessellationStateCreateInfo = null,
|
||||
viewport_state: ?PipelineViewportStateCreateInfo = null,
|
||||
rasterization_state: ?vk.PipelineRasterizationStateCreateInfo = null,
|
||||
multisample_state: ?vk.PipelineMultisampleStateCreateInfo = null,
|
||||
depth_stencil_state: ?vk.PipelineDepthStencilStateCreateInfo = null,
|
||||
color_blend_state: ?PipelineColorBlendStateCreateInfo = null,
|
||||
dynamic_state: ?PipelineDynamicStateCreateInfo = null,
|
||||
layout: vk.PipelineLayout = .null_handle,
|
||||
render_pass: vk.RenderPass = .null_handle,
|
||||
subpass: u32,
|
||||
base_pipeline_handle: vk.Pipeline = .null_handle,
|
||||
};
|
||||
|
||||
pub const ImageCreateInfo = struct {
|
||||
flags: vk.ImageCreateFlags = .{},
|
||||
image_type: vk.ImageType,
|
||||
format: vk.Format,
|
||||
extent: vk.Extent3D,
|
||||
mip_levels: u32,
|
||||
array_layers: u32,
|
||||
samples: vk.SampleCountFlags,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
queue_family_indices: []const u32 = &.{},
|
||||
initial_layout: vk.ImageLayout,
|
||||
};
|
||||
|
||||
pub const ImageViewCreateInfo = struct {
|
||||
flags: vk.ImageViewCreateFlags = .{},
|
||||
image: vk.Image,
|
||||
view_type: vk.ImageViewType,
|
||||
format: vk.Format,
|
||||
components: vk.ComponentMapping = .{
|
||||
.r = .identity,
|
||||
.g = .identity,
|
||||
.b = .identity,
|
||||
.a = .identity,
|
||||
},
|
||||
subresource_range: vk.ImageSubresourceRange,
|
||||
};
|
||||
|
||||
pub const PipelineColorBlendStateCreateInfo = struct {
|
||||
flags: vk.PipelineColorBlendStateCreateFlags = .{},
|
||||
logic_op_enable: vk.Bool32,
|
||||
logic_op: vk.LogicOp,
|
||||
attachments: []const vk.PipelineColorBlendAttachmentState = &.{},
|
||||
blend_constants: [4]f32,
|
||||
};
|
||||
|
||||
pub const PipelineDynamicStateCreateInfo = struct {
|
||||
flags: vk.PipelineDynamicStateCreateFlags = .{},
|
||||
dynamic_states: []const vk.DynamicState = &.{},
|
||||
};
|
||||
|
||||
pub const PipelineLayoutCreateInfo = struct {
|
||||
flags: vk.PipelineLayoutCreateFlags = .{},
|
||||
set_layouts: []const vk.DescriptorSetLayout = &.{},
|
||||
push_constant_ranges: []const vk.PushConstantRange = &.{},
|
||||
};
|
||||
|
||||
pub const PipelineShaderStageCreateInfo = struct {
|
||||
flags: vk.PipelineShaderStageCreateFlags = .{},
|
||||
stage: vk.ShaderStageFlags,
|
||||
module: vk.ShaderModule = .null_handle,
|
||||
name: [*:0]const u8,
|
||||
specialization_info: ?SpecializationInfo = null,
|
||||
};
|
||||
|
||||
pub const PipelineVertexInputStateCreateInfo = struct {
|
||||
flags: vk.PipelineVertexInputStateCreateFlags = .{},
|
||||
vertex_binding_descriptions: []const vk.VertexInputBindingDescription = &.{},
|
||||
vertex_attribute_descriptions: []const vk.VertexInputAttributeDescription = &.{},
|
||||
};
|
||||
|
||||
pub const PipelineViewportStateCreateInfo = struct {
|
||||
flags: vk.PipelineViewportStateCreateFlags = .{},
|
||||
viewports: []const vk.Viewport = &.{},
|
||||
scissors: []const vk.Rect2D = &.{},
|
||||
};
|
||||
|
||||
pub const ShaderModuleCreateInfo = struct {
|
||||
flags: vk.ShaderModuleCreateFlags = .{},
|
||||
code: []align(@alignOf(u32)) const u8,
|
||||
};
|
||||
|
||||
pub const SpecializationInfo = struct {
|
||||
map_entries: []vk.SpecializationMapEntry = &.{},
|
||||
data: []const u8 = &.{},
|
||||
};
|
||||
|
||||
pub const WriteDescriptorSet = struct {
|
||||
dst_set: vk.DescriptorSet,
|
||||
dst_binding: u32,
|
||||
dst_array_element: u32,
|
||||
descriptor_type: vk.DescriptorType,
|
||||
descriptor_infos: union(enum) {
|
||||
image: []const vk.DescriptorImageInfo,
|
||||
buffer: []const vk.DescriptorBufferInfo,
|
||||
texel_buffer_view: []const vk.BufferView,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn createBuffer(self: *Engine, create_info: BufferCreateInfo) !vk.Buffer {
|
||||
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{};
|
||||
for (create_info.queue_family_indices) |queue_family_index| {
|
||||
try queue_family_indices_set.put(allocator, queue_family_index, {});
|
||||
}
|
||||
|
||||
const queue_family_indices = queue_family_indices_set.keys();
|
||||
|
||||
const buffer = self.device.createBuffer(&.{
|
||||
.flags = create_info.flags,
|
||||
.size = create_info.size,
|
||||
.usage = create_info.usage,
|
||||
.sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive,
|
||||
.queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0,
|
||||
.p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null,
|
||||
}, &self.vk_allocator.interface);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void {
|
||||
std.debug.assert(descriptor_sets.len >= allocate_info.set_layouts.len);
|
||||
|
||||
const has_variable_descriptor_counts = allocate_info.variable_descriptor_counts.len > 0;
|
||||
|
||||
var p_next: ?*const anyopaque = null;
|
||||
|
||||
if (has_variable_descriptor_counts) {
|
||||
p_next = &vk.DescriptorSetVariableDescriptorCountAllocateInfo{
|
||||
.p_next = p_next,
|
||||
.descriptor_set_count = @intCast(allocate_info.variable_descriptor_counts.len),
|
||||
.p_descriptor_counts = allocate_info.variable_descriptor_counts.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
try self.device.allocateDescriptorSets(&.{
|
||||
.p_next = p_next,
|
||||
.descriptor_pool = allocate_info.descriptor_pool,
|
||||
.descriptor_set_count = @intCast(allocate_info.set_layouts.len),
|
||||
.p_set_layouts = allocate_info.set_layouts.ptr,
|
||||
}, descriptor_sets.ptr);
|
||||
}
|
||||
|
||||
pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayoutCreateInfo) !vk.DescriptorSetLayout {
|
||||
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
const has_binding_flags = blk: {
|
||||
for (create_info.bindings) |binding| {
|
||||
if (@as(vk.Flags, @bitCast(binding.flags)) != 0) {
|
||||
break :blk true;
|
||||
}
|
||||
}
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
var p_next: ?*const anyopaque = null;
|
||||
|
||||
if (has_binding_flags) {
|
||||
const descriptor_set_layout_bindings_flags = try allocator.alloc(vk.DescriptorBindingFlags, create_info.bindings.len);
|
||||
for (descriptor_set_layout_bindings_flags, create_info.bindings) |*x, binding| {
|
||||
x.* = binding.flags;
|
||||
}
|
||||
p_next = &vk.DescriptorSetLayoutBindingFlagsCreateInfo{
|
||||
.p_next = p_next,
|
||||
.binding_count = @intCast(descriptor_set_layout_bindings_flags.len),
|
||||
.p_binding_flags = descriptor_set_layout_bindings_flags.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
const bindings = try allocator.alloc(vk.DescriptorSetLayoutBinding, create_info.bindings.len);
|
||||
for (bindings, create_info.bindings) |*x, binding| {
|
||||
x.* = .{
|
||||
.binding = binding.binding,
|
||||
.descriptor_type = binding.descriptor_type,
|
||||
.descriptor_count = binding.descriptor_count,
|
||||
.stage_flags = binding.stage_flags,
|
||||
.p_immutable_samplers = binding.immutable_samplers.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
const descriptor_set_layout = try self.device.createDescriptorSetLayout(&.{
|
||||
.p_next = p_next,
|
||||
.flags = create_info.flags,
|
||||
.binding_count = @intCast(bindings.len),
|
||||
.p_bindings = bindings.ptr,
|
||||
}, &self.vk_allocator.interface);
|
||||
return descriptor_set_layout;
|
||||
}
|
||||
|
||||
pub fn createDescriptorPool(self: *Engine, create_info: DescriptorPoolCreateInfo) !vk.DescriptorPool {
|
||||
const descriptor_pool = try self.device.createDescriptorPool(&.{
|
||||
.flags = create_info.flags,
|
||||
.max_sets = create_info.max_sets,
|
||||
.pool_size_count = @intCast(create_info.pool_sizes.len),
|
||||
.p_pool_sizes = create_info.pool_sizes.ptr,
|
||||
}, &self.vk_allocator.interface);
|
||||
return descriptor_pool;
|
||||
}
|
||||
|
||||
pub fn createFence(self: *Engine, create_info: vk.FenceCreateInfo) !vk.Fence {
|
||||
const fence = try self.device.createFence(&create_info, &self.vk_allocator.interface);
|
||||
return fence;
|
||||
}
|
||||
|
||||
pub fn createFramebuffer(self: *Engine, create_info: FramebufferCreateInfo) !vk.Framebuffer {
|
||||
const framebuffer = try self.device.createFramebuffer(&.{
|
||||
.flags = create_info.flags,
|
||||
.render_pass = create_info.render_pass,
|
||||
.attachment_count = @intCast(create_info.attachments.len),
|
||||
.p_attachments = create_info.attachments.ptr,
|
||||
.width = create_info.width,
|
||||
.height = create_info.height,
|
||||
.layers = create_info.layers,
|
||||
}, &self.vk_allocator.interface);
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreateInfo) !vk.Pipeline {
|
||||
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
const stages = try allocator.alloc(vk.PipelineShaderStageCreateInfo, create_info.stages.len);
|
||||
for (stages, create_info.stages) |*x, *stage| {
|
||||
x.* = .{
|
||||
.flags = stage.flags,
|
||||
.stage = stage.stage,
|
||||
.p_name = stage.name,
|
||||
.module = stage.module,
|
||||
.p_specialization_info = blk: {
|
||||
if (stage.specialization_info) |y| {
|
||||
const specialization_info = try allocator.create(vk.SpecializationInfo);
|
||||
specialization_info.* = .{
|
||||
.map_entry_count = @intCast(y.map_entries.len),
|
||||
.p_map_entries = y.map_entries.ptr,
|
||||
.data_size = y.data.len,
|
||||
.p_data = y.data.ptr,
|
||||
};
|
||||
break :blk specialization_info;
|
||||
} else {
|
||||
break :blk null;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const graphics_pipeline_create_infos = [_]vk.GraphicsPipelineCreateInfo{
|
||||
.{
|
||||
.flags = create_info.flags,
|
||||
.stage_count = @intCast(stages.len),
|
||||
.p_stages = stages.ptr,
|
||||
.p_vertex_input_state = if (create_info.vertex_input_state) |vertex_input_state| &.{
|
||||
.flags = vertex_input_state.flags,
|
||||
.vertex_binding_description_count = @intCast(vertex_input_state.vertex_binding_descriptions.len),
|
||||
.p_vertex_binding_descriptions = vertex_input_state.vertex_binding_descriptions.ptr,
|
||||
.vertex_attribute_description_count = @intCast(vertex_input_state.vertex_attribute_descriptions.len),
|
||||
.p_vertex_attribute_descriptions = vertex_input_state.vertex_attribute_descriptions.ptr,
|
||||
} else null,
|
||||
.p_input_assembly_state = if (create_info.input_assembly_state) |*x| x else null,
|
||||
.p_tessellation_state = if (create_info.tessellation_state) |*x| x else null,
|
||||
.p_viewport_state = if (create_info.viewport_state) |viewport_state| &.{
|
||||
.flags = viewport_state.flags,
|
||||
.viewport_count = @intCast(viewport_state.viewports.len),
|
||||
.p_viewports = viewport_state.viewports.ptr,
|
||||
.scissor_count = @intCast(viewport_state.scissors.len),
|
||||
.p_scissors = viewport_state.scissors.ptr,
|
||||
} else null,
|
||||
.p_rasterization_state = if (create_info.rasterization_state) |*x| x else null,
|
||||
.p_multisample_state = if (create_info.multisample_state) |*x| x else null,
|
||||
.p_depth_stencil_state = if (create_info.depth_stencil_state) |*x| x else null,
|
||||
.p_color_blend_state = if (create_info.color_blend_state) |color_blend_state| &.{
|
||||
.flags = color_blend_state.flags,
|
||||
.logic_op_enable = color_blend_state.logic_op_enable,
|
||||
.logic_op = color_blend_state.logic_op,
|
||||
.attachment_count = @intCast(color_blend_state.attachments.len),
|
||||
.p_attachments = color_blend_state.attachments.ptr,
|
||||
.blend_constants = color_blend_state.blend_constants,
|
||||
} else null,
|
||||
.p_dynamic_state = if (create_info.dynamic_state) |dynamic_state| &.{
|
||||
.flags = dynamic_state.flags,
|
||||
.dynamic_state_count = @intCast(dynamic_state.dynamic_states.len),
|
||||
.p_dynamic_states = dynamic_state.dynamic_states.ptr,
|
||||
} else null,
|
||||
.layout = create_info.layout,
|
||||
.render_pass = create_info.render_pass,
|
||||
.subpass = create_info.subpass,
|
||||
.base_pipeline_handle = create_info.base_pipeline_handle,
|
||||
.base_pipeline_index = -1,
|
||||
},
|
||||
};
|
||||
|
||||
var pipelines: [1]vk.Pipeline = undefined;
|
||||
_ = try self.device.createGraphicsPipelines(.null_handle, graphics_pipeline_create_infos.len, &graphics_pipeline_create_infos, &self.vk_allocator.interface, &pipelines);
|
||||
return pipelines[0];
|
||||
}
|
||||
|
||||
pub fn createImage(self: *Engine, create_info: ImageCreateInfo) !vk.Image {
|
||||
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{};
|
||||
for (create_info.queue_family_indices) |queue_family_index| {
|
||||
try queue_family_indices_set.put(allocator, queue_family_index, {});
|
||||
}
|
||||
|
||||
const queue_family_indices = queue_family_indices_set.keys();
|
||||
|
||||
const image = self.device.createImage(&.{
|
||||
.flags = create_info.flags,
|
||||
.image_type = create_info.image_type,
|
||||
.format = create_info.format,
|
||||
.extent = create_info.extent,
|
||||
.mip_levels = create_info.mip_levels,
|
||||
.array_layers = create_info.array_layers,
|
||||
.samples = create_info.samples,
|
||||
.tiling = create_info.tiling,
|
||||
.usage = create_info.usage,
|
||||
.sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive,
|
||||
.queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0,
|
||||
.p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null,
|
||||
.initial_layout = create_info.initial_layout,
|
||||
}, &self.vk_allocator.interface);
|
||||
return image;
|
||||
}
|
||||
|
||||
pub fn createImageView(self: *Engine, create_info: ImageViewCreateInfo) !vk.ImageView {
|
||||
const image_view = try self.device.createImageView(&.{
|
||||
.flags = create_info.flags,
|
||||
.image = create_info.image,
|
||||
.view_type = create_info.view_type,
|
||||
.format = create_info.format,
|
||||
.components = create_info.components,
|
||||
.subresource_range = create_info.subresource_range,
|
||||
}, &self.vk_allocator.interface);
|
||||
return image_view;
|
||||
}
|
||||
|
||||
pub fn createPipelineLayout(self: *Engine, create_info: PipelineLayoutCreateInfo) !vk.PipelineLayout {
|
||||
const pipeline_layout = try self.device.createPipelineLayout(&.{
|
||||
.flags = create_info.flags,
|
||||
.set_layout_count = @intCast(create_info.set_layouts.len),
|
||||
.p_set_layouts = create_info.set_layouts.ptr,
|
||||
.push_constant_range_count = @intCast(create_info.push_constant_ranges.len),
|
||||
.p_push_constant_ranges = create_info.push_constant_ranges.ptr,
|
||||
}, &self.vk_allocator.interface);
|
||||
return pipeline_layout;
|
||||
}
|
||||
|
||||
pub fn createSampler(self: *Engine, create_info: vk.SamplerCreateInfo) !vk.Sampler {
|
||||
const sampler = try self.device.createSampler(&create_info, &self.vk_allocator.interface);
|
||||
return sampler;
|
||||
}
|
||||
|
||||
pub fn createSemaphore(self: *Engine) !vk.Semaphore {
|
||||
const semaphore = try self.device.createSemaphore(&.{}, &self.vk_allocator.interface);
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
pub fn createShaderModule(self: *Engine, create_info: ShaderModuleCreateInfo) !vk.ShaderModule {
|
||||
const shader_module = try self.device.createShaderModule(&.{
|
||||
.flags = create_info.flags,
|
||||
.code_size = create_info.code.len,
|
||||
.p_code = @ptrCast(create_info.code.ptr),
|
||||
}, &self.vk_allocator.interface);
|
||||
return shader_module;
|
||||
}
|
||||
|
||||
pub fn destroyBuffer(self: *Engine, buffer: vk.Buffer) void {
|
||||
self.device.destroyBuffer(buffer, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyDescriptorPool(self: *Engine, descriptor_pool: vk.DescriptorPool) void {
|
||||
self.device.destroyDescriptorPool(descriptor_pool, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyDescriptorSetLayout(self: *Engine, descriptor_set_layout: vk.DescriptorSetLayout) void {
|
||||
self.device.destroyDescriptorSetLayout(descriptor_set_layout, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyFence(self: *Engine, fence: vk.Fence) void {
|
||||
self.device.destroyFence(fence, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyFramebuffer(self: *Engine, framebuffer: vk.Framebuffer) void {
|
||||
self.device.destroyFramebuffer(framebuffer, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyImage(self: *Engine, image: vk.Image) void {
|
||||
self.device.destroyImage(image, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyImageView(self: *Engine, image_view: vk.ImageView) void {
|
||||
self.device.destroyImageView(image_view, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyPipeline(self: *Engine, pipeline: vk.Pipeline) void {
|
||||
self.device.destroyPipeline(pipeline, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyPipelineLayout(self: *Engine, pipeline_layout: vk.PipelineLayout) void {
|
||||
self.device.destroyPipelineLayout(pipeline_layout, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroySampler(self: *Engine, sampler: vk.Sampler) void {
|
||||
self.device.destroySampler(sampler, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroySemaphore(self: *Engine, semaphore: vk.Semaphore) void {
|
||||
self.device.destroySemaphore(semaphore, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn destroyShaderModule(self: *Engine, shader_module: vk.ShaderModule) void {
|
||||
self.device.destroyShaderModule(shader_module, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn freeMemory(self: *Engine, device_memory: vk.DeviceMemory) void {
|
||||
self.device.freeMemory(device_memory, &self.vk_allocator.interface);
|
||||
}
|
||||
|
||||
pub fn resetFence(self: *Engine, fence: vk.Fence) !void {
|
||||
try self.device.resetFences(1, @ptrCast(&fence));
|
||||
}
|
||||
|
||||
pub fn updateDescriptorSets(self: *Engine, update_info: DescriptorSetsUpdateInfo) !void {
|
||||
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
const descriptor_writes = try allocator.alloc(vk.WriteDescriptorSet, update_info.writes.len);
|
||||
for (descriptor_writes, update_info.writes) |*x, write| {
|
||||
x.* = switch (write.descriptor_infos) {
|
||||
.image => |y| .{
|
||||
.dst_set = write.dst_set,
|
||||
.dst_binding = write.dst_binding,
|
||||
.dst_array_element = write.dst_array_element,
|
||||
.descriptor_count = @intCast(y.len),
|
||||
.descriptor_type = write.descriptor_type,
|
||||
.p_image_info = y.ptr,
|
||||
.p_buffer_info = &.{},
|
||||
.p_texel_buffer_view = &.{},
|
||||
},
|
||||
.buffer => |y| .{
|
||||
.dst_set = write.dst_set,
|
||||
.dst_binding = write.dst_binding,
|
||||
.dst_array_element = write.dst_array_element,
|
||||
.descriptor_count = @intCast(y.len),
|
||||
.descriptor_type = write.descriptor_type,
|
||||
.p_image_info = &.{},
|
||||
.p_buffer_info = y.ptr,
|
||||
.p_texel_buffer_view = &.{},
|
||||
},
|
||||
.texel_buffer_view => |y| .{
|
||||
.dst_set = write.dst_set,
|
||||
.dst_binding = write.dst_binding,
|
||||
.dst_array_element = write.dst_array_element,
|
||||
.descriptor_count = @intCast(y.len),
|
||||
.descriptor_type = write.descriptor_type,
|
||||
.p_image_info = &.{},
|
||||
.p_buffer_info = &.{},
|
||||
.p_texel_buffer_view = y.ptr,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
self.device.updateDescriptorSets(
|
||||
@intCast(descriptor_writes.len),
|
||||
descriptor_writes.ptr,
|
||||
@intCast(update_info.copies.len),
|
||||
update_info.copies.ptr,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn waitForFence(self: *Engine, fence: vk.Fence) !void {
|
||||
_ = try self.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
}
|
||||
|
||||
210
src/engine/GenericBuffer.zig
Normal file
210
src/engine/GenericBuffer.zig
Normal file
@@ -0,0 +1,210 @@
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
||||
|
||||
pub const Usage = enum {
|
||||
uniform,
|
||||
storage,
|
||||
index,
|
||||
vertex,
|
||||
indirect,
|
||||
|
||||
pub fn asBufferUsageFlags(self: Usage) vk.BufferUsageFlags {
|
||||
return switch (self) {
|
||||
.uniform => .{ .transfer_dst_bit = true, .uniform_buffer_bit = true },
|
||||
.storage => .{ .transfer_dst_bit = true, .storage_buffer_bit = true },
|
||||
.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 },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const InitInfo = struct {
|
||||
usage: Usage,
|
||||
target_queue: TargetQueue,
|
||||
array_capacity: u32 = 0,
|
||||
};
|
||||
|
||||
pub fn GenericBuffer(comptime Header: type, comptime Element: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
buffer: vk.Buffer,
|
||||
device_memory: vk.DeviceMemory,
|
||||
target_queue: TargetQueue,
|
||||
array_capacity: u32,
|
||||
|
||||
const header_size: u32 = @sizeOf(Header);
|
||||
const element_size: u32 = @sizeOf(Element);
|
||||
const array_offset: u32 = std.mem.alignForward(usize, header_size, @alignOf(Element));
|
||||
|
||||
pub const WriteInfo = struct {
|
||||
header: ?Header = null,
|
||||
element_offset: u32 = 0,
|
||||
elements: []const Element = &.{},
|
||||
};
|
||||
|
||||
pub fn init(engine: *Engine, init_info: InitInfo) !Self {
|
||||
const array_size = try std.math.mul(u32, init_info.array_capacity, element_size);
|
||||
const size = try std.math.add(u32, array_offset, array_size);
|
||||
|
||||
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 = size,
|
||||
.usage = init_info.usage.asBufferUsageFlags(),
|
||||
.queue_family_indices = &.{ target_queue_family, transfer_queue_family },
|
||||
});
|
||||
errdefer engine.destroyBuffer(buffer);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.freeMemory(device_memory);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, device_memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.device_memory = device_memory,
|
||||
.target_queue = init_info.target_queue,
|
||||
.array_capacity = init_info.array_capacity,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, engine: *Engine) void {
|
||||
engine.freeMemory(self.device_memory);
|
||||
engine.destroyBuffer(self.buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: Self, engine: *Engine, write_info: WriteInfo) !void {
|
||||
const element_count = std.math.cast(u32, write_info.elements.len) orelse return error.Overflow;
|
||||
std.debug.assert(write_info.element_offset + element_count <= self.array_capacity);
|
||||
|
||||
const header_write_size: u32 = if (write_info.header != null) header_size else 0;
|
||||
const array_write_size = element_count * element_size;
|
||||
const array_write_offset: u32 = if (header_write_size > 0) array_offset else 0;
|
||||
const write_size = array_write_offset + array_write_size;
|
||||
|
||||
if (write_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var regions_buffer: [2]vk.BufferCopy = undefined;
|
||||
var regions: std.ArrayList(vk.BufferCopy) = .initBuffer(®ions_buffer);
|
||||
|
||||
// One continuous write
|
||||
if (header_write_size > 0 and array_write_size > 0 and write_info.element_offset == 0) {
|
||||
regions.appendAssumeCapacity(.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = write_size,
|
||||
});
|
||||
}
|
||||
// Separate writes for header and array (if they exist)
|
||||
else {
|
||||
if (header_write_size > 0) {
|
||||
regions.appendAssumeCapacity(.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = header_write_size,
|
||||
});
|
||||
}
|
||||
if (array_write_size > 0) {
|
||||
regions.appendAssumeCapacity(.{
|
||||
.src_offset = array_write_offset,
|
||||
.dst_offset = array_offset + write_info.element_offset * element_size,
|
||||
.size = array_write_size,
|
||||
});
|
||||
}
|
||||
|
||||
std.debug.assert(regions.items.len > 0);
|
||||
}
|
||||
|
||||
var staging_buffer: StagingBuffer = try .init(engine, .{
|
||||
.target_queue = self.target_queue,
|
||||
.capacity = write_size,
|
||||
});
|
||||
defer staging_buffer.deinit(engine);
|
||||
|
||||
const staging_memory = try staging_buffer.map(engine);
|
||||
if (write_info.header) |header| {
|
||||
@memcpy(staging_memory[0..header_size], std.mem.asBytes(&header));
|
||||
}
|
||||
@memcpy(staging_memory[array_write_offset..write_size], std.mem.sliceAsBytes(write_info.elements));
|
||||
staging_buffer.unmap(engine);
|
||||
|
||||
const command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(command_buffer);
|
||||
|
||||
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
command_buffer.copyBuffer(
|
||||
staging_buffer.buffer,
|
||||
self.buffer,
|
||||
@intCast(regions.items.len),
|
||||
regions.items.ptr,
|
||||
);
|
||||
try command_buffer.endCommandBuffer();
|
||||
|
||||
const fence = try engine.createFence(.{});
|
||||
defer engine.destroyFence(fence);
|
||||
|
||||
try engine.submitTransferCommandBuffer(command_buffer, fence);
|
||||
try engine.waitForFence(fence);
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: Self, engine: *Engine, data_offset: u32, data: []const u8) !void {
|
||||
const array_size = self.array_capacity * element_size;
|
||||
const size = array_offset + array_size;
|
||||
|
||||
const data_size = std.math.cast(u32, data.len) orelse return error.Overflow;
|
||||
std.debug.assert(data_offset + data_size <= size);
|
||||
|
||||
const regions = [_]vk.BufferCopy{
|
||||
.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = data_offset,
|
||||
.size = data_size,
|
||||
},
|
||||
};
|
||||
|
||||
var staging_buffer = try StagingBuffer.init(engine, .{
|
||||
.target_queue = self.target_queue,
|
||||
.capacity = data_size,
|
||||
});
|
||||
defer staging_buffer.deinit(engine);
|
||||
|
||||
const staging_memory = try staging_buffer.map(engine);
|
||||
@memcpy(staging_memory, data);
|
||||
staging_buffer.unmap(engine);
|
||||
|
||||
const command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(command_buffer);
|
||||
|
||||
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
command_buffer.copyBuffer(
|
||||
staging_buffer.buffer,
|
||||
self.buffer,
|
||||
@intCast(regions.items.len),
|
||||
regions.items.ptr,
|
||||
);
|
||||
try command_buffer.endCommandBuffer();
|
||||
|
||||
const fence = try engine.createFence(.{});
|
||||
defer engine.destroyFence(fence);
|
||||
|
||||
try engine.submitTransferCommandBuffer(command_buffer, fence);
|
||||
try engine.waitForFence(fence);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
const IndexBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
const QSM = @import("QueueSharingMode.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
index_count: usize,
|
||||
|
||||
pub fn init(engine: *Engine, index_count: usize) !IndexBuffer {
|
||||
const size = std.math.mul(usize, index_count, @sizeOf(u16)) catch return error.OutOfMemory;
|
||||
|
||||
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = size,
|
||||
.usage = .{
|
||||
.transfer_dst_bit = true,
|
||||
.index_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = qsm.sharing_mode,
|
||||
.queue_family_index_count = qsm.queue_family_index_count,
|
||||
.p_queue_family_indices = qsm.p_queue_family_indices,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.index_count = index_count,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *IndexBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: IndexBuffer, engine: *Engine, indices: []const u16) !void {
|
||||
std.debug.assert(indices.len == self.index_count);
|
||||
|
||||
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
|
||||
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||
|
||||
const size = std.mem.sliceAsBytes(indices).len;
|
||||
|
||||
var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(indices), engine.graphics_queue.allocation.family);
|
||||
defer staging_buffer.deinit(engine);
|
||||
|
||||
const command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(command_buffer);
|
||||
|
||||
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
const regions = [_]vk.BufferCopy{
|
||||
.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = size,
|
||||
},
|
||||
};
|
||||
|
||||
command_buffer.copyBuffer(
|
||||
staging_buffer.buffer,
|
||||
self.buffer,
|
||||
regions.len,
|
||||
®ions,
|
||||
);
|
||||
|
||||
try command_buffer.endCommandBuffer();
|
||||
try engine.submitTransferCommandBuffer(command_buffer, fence);
|
||||
|
||||
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
}
|
||||
@@ -4,51 +4,69 @@ const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const QSM = @import("QueueSharingMode.zig");
|
||||
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
device_memory: vk.DeviceMemory,
|
||||
capacity: u32,
|
||||
|
||||
pub fn init(engine: *Engine, data: []const u8, destination_queue_family: u32) !StagingBuffer {
|
||||
pub const InitInfo = struct {
|
||||
target_queue: TargetQueue,
|
||||
capacity: u32,
|
||||
};
|
||||
|
||||
pub fn init(engine: *Engine, init_info: InitInfo) !StagingBuffer {
|
||||
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 qsm = QSM.resolve(destination_queue_family, transfer_queue_family);
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = data.len,
|
||||
const buffer = try engine.createBuffer(.{
|
||||
.size = init_info.capacity,
|
||||
.usage = .{
|
||||
.transfer_src_bit = true,
|
||||
},
|
||||
.sharing_mode = qsm.sharing_mode,
|
||||
.queue_family_index_count = qsm.queue_family_index_count,
|
||||
.p_queue_family_indices = qsm.p_queue_family_indices,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
|
||||
.queue_family_indices = &.{ target_queue_family, transfer_queue_family },
|
||||
});
|
||||
errdefer engine.destroyBuffer(buffer);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(
|
||||
const device_memory = try engine.allocate(
|
||||
memory_requirements,
|
||||
.{
|
||||
.host_visible_bit = true,
|
||||
.host_coherent_bit = true,
|
||||
},
|
||||
);
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
errdefer engine.freeMemory(device_memory);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
const mapped_memory: [*]u8 = @ptrCast(try engine.device.mapMemory(memory, 0, data.len, .{}) orelse return error.OutOfMemory);
|
||||
defer engine.device.unmapMemory(memory);
|
||||
@memcpy(mapped_memory, data);
|
||||
try engine.device.bindBufferMemory(buffer, device_memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.device_memory = device_memory,
|
||||
.capacity = init_info.capacity,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StagingBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
|
||||
engine.freeMemory(self.device_memory);
|
||||
engine.destroyBuffer(self.buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn map(self: StagingBuffer, engine: *Engine) ![]u8 {
|
||||
const mapped_memory = try self.mapPartial(engine, 0, self.capacity);
|
||||
return mapped_memory;
|
||||
}
|
||||
|
||||
pub fn mapPartial(self: StagingBuffer, engine: *Engine, offset: u32, len: u32) ![]u8 {
|
||||
const mapped_memory = try engine.device.mapMemory(self.device_memory, offset, len, .{});
|
||||
return @as([*]u8, @ptrCast(mapped_memory))[0..len];
|
||||
}
|
||||
|
||||
pub fn unmap(self: StagingBuffer, engine: *Engine) void {
|
||||
engine.device.unmapMemory(self.device_memory);
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
const StorageBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
const QSM = @import("QueueSharingMode.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
prefix_size: usize,
|
||||
element_size: usize,
|
||||
array_offset: usize,
|
||||
array_capacity: usize,
|
||||
|
||||
pub fn init(engine: *Engine, comptime PrefixType: type, comptime ElementType: type, array_capacity: usize) !StorageBuffer {
|
||||
const prefix_size = @sizeOf(PrefixType);
|
||||
const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType));
|
||||
const element_size = @sizeOf(ElementType);
|
||||
|
||||
const array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory;
|
||||
const size = std.math.add(usize, array_offset, array_capacity_in_bytes) catch return error.OutOfMemory;
|
||||
|
||||
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = size,
|
||||
.usage = .{
|
||||
.transfer_src_bit = true,
|
||||
.transfer_dst_bit = true,
|
||||
.storage_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = qsm.sharing_mode,
|
||||
.queue_family_index_count = qsm.queue_family_index_count,
|
||||
.p_queue_family_indices = qsm.p_queue_family_indices,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.prefix_size = prefix_size,
|
||||
.element_size = element_size,
|
||||
.array_offset = array_offset,
|
||||
.array_capacity = array_capacity,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StorageBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn enlarge(self: *StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, array_capacity: usize) !void {
|
||||
std.debug.assert(array_capacity > self.array_capacity);
|
||||
std.debug.assert(@sizeOf(PrefixType) == self.prefix_size);
|
||||
std.debug.assert(@sizeOf(ElementType) == self.element_size);
|
||||
std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType)));
|
||||
|
||||
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
|
||||
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||
|
||||
const prefix_size = @sizeOf(PrefixType);
|
||||
const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType));
|
||||
const element_size = @sizeOf(ElementType);
|
||||
|
||||
const old_array_size_in_bytes = self.array_capacity * @sizeOf(ElementType);
|
||||
const old_size = self.array_offset + old_array_size_in_bytes;
|
||||
|
||||
const new_array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory;
|
||||
const new_size = std.math.add(usize, array_offset, new_array_capacity_in_bytes) catch return error.OutOfMemory;
|
||||
|
||||
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = new_size,
|
||||
.usage = .{
|
||||
.transfer_src_bit = true,
|
||||
.transfer_dst_bit = true,
|
||||
.storage_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = qsm.sharing_mode,
|
||||
.queue_family_index_count = qsm.queue_family_index_count,
|
||||
.p_queue_family_indices = qsm.p_queue_family_indices,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
const command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(command_buffer);
|
||||
|
||||
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
const regions = [_]vk.BufferCopy{
|
||||
.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = old_size,
|
||||
},
|
||||
};
|
||||
|
||||
command_buffer.copyBuffer(
|
||||
self.buffer,
|
||||
buffer,
|
||||
regions.len,
|
||||
®ions,
|
||||
);
|
||||
|
||||
try command_buffer.endCommandBuffer();
|
||||
try engine.submitTransferCommandBuffer(command_buffer, fence);
|
||||
|
||||
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
|
||||
|
||||
self.buffer = buffer;
|
||||
self.memory = memory;
|
||||
self.array_capacity = array_capacity;
|
||||
}
|
||||
|
||||
pub fn write(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, elements: []const ElementType) !void {
|
||||
try self.writeOffset(PrefixType, ElementType, engine, prefix, 0, elements);
|
||||
}
|
||||
|
||||
pub fn writeOffset(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, offset: usize, elements: []const ElementType) !void {
|
||||
std.debug.assert(offset + elements.len <= self.array_capacity);
|
||||
std.debug.assert(@sizeOf(PrefixType) == self.prefix_size);
|
||||
std.debug.assert(@sizeOf(ElementType) == self.element_size);
|
||||
std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType)));
|
||||
|
||||
const array_size_in_bytes = elements.len * @sizeOf(ElementType);
|
||||
const size = self.array_offset + array_size_in_bytes;
|
||||
|
||||
var regions_buffer: [2]vk.BufferCopy = undefined;
|
||||
var regions: std.ArrayList(vk.BufferCopy) = .initBuffer(®ions_buffer);
|
||||
|
||||
if (self.prefix_size > 0) {
|
||||
regions.appendAssumeCapacity(.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = self.prefix_size,
|
||||
});
|
||||
}
|
||||
|
||||
if (array_size_in_bytes > 0) {
|
||||
regions.appendAssumeCapacity(.{
|
||||
.src_offset = self.array_offset,
|
||||
.dst_offset = self.array_offset + offset * @sizeOf(ElementType),
|
||||
.size = array_size_in_bytes,
|
||||
});
|
||||
}
|
||||
|
||||
if (regions.items.len == 0) {
|
||||
std.log.warn("Zero-length StorageBuffer({s}, {s}) write", .{ @typeName(PrefixType), @typeName(ElementType) });
|
||||
return;
|
||||
}
|
||||
|
||||
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
|
||||
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||
|
||||
const data = try engine.vk_allocator.allocator.alloc(u8, size);
|
||||
defer engine.vk_allocator.allocator.free(data);
|
||||
|
||||
@memcpy(data[0..@sizeOf(PrefixType)], std.mem.asBytes(&prefix));
|
||||
@memcpy(data[self.array_offset..], std.mem.sliceAsBytes(elements));
|
||||
|
||||
var staging_buffer: StagingBuffer = try .init(engine, data, engine.graphics_queue.allocation.family);
|
||||
defer staging_buffer.deinit(engine);
|
||||
|
||||
const command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(command_buffer);
|
||||
|
||||
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
command_buffer.copyBuffer(
|
||||
staging_buffer.buffer,
|
||||
self.buffer,
|
||||
@intCast(regions.items.len),
|
||||
regions.items.ptr,
|
||||
);
|
||||
|
||||
try command_buffer.endCommandBuffer();
|
||||
|
||||
try engine.submitTransferCommandBuffer(command_buffer, fence);
|
||||
|
||||
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
}
|
||||
@@ -6,11 +6,19 @@ const vk = @import("vulkan");
|
||||
const Engine = @import("Engine.zig");
|
||||
const QSM = @import("QueueSharingMode.zig");
|
||||
|
||||
engine: *Engine,
|
||||
params: Params,
|
||||
render_pass: vk.RenderPass,
|
||||
|
||||
extent: vk.Extent2D = .{ .width = 0, .height = 0 },
|
||||
swapchain: vk.SwapchainKHR = .null_handle,
|
||||
swapchain_images: []SwapchainImage = &.{},
|
||||
image_index: u32 = 0,
|
||||
semaphore_image_acquired: vk.Semaphore = .null_handle,
|
||||
|
||||
pub const PresentResult = enum {
|
||||
optimal,
|
||||
suboptimal,
|
||||
};
|
||||
|
||||
pub fn init(engine: *Engine) !Swapchain {
|
||||
const params: Params = try .init(engine);
|
||||
@@ -20,7 +28,7 @@ pub fn init(engine: *Engine) !Swapchain {
|
||||
.{
|
||||
.format = params.surface_format.format,
|
||||
.samples = .{ .@"1_bit" = true },
|
||||
.load_op = .dont_care,
|
||||
.load_op = .clear,
|
||||
.store_op = .store,
|
||||
.stencil_load_op = .dont_care,
|
||||
.stencil_store_op = .dont_care,
|
||||
@@ -54,47 +62,49 @@ pub fn init(engine: *Engine) !Swapchain {
|
||||
errdefer engine.device.destroyRenderPass(render_pass, &engine.vk_allocator.interface);
|
||||
|
||||
var swapchain: Swapchain = .{
|
||||
.engine = engine,
|
||||
.params = params,
|
||||
.render_pass = render_pass,
|
||||
};
|
||||
|
||||
try recreate(&swapchain);
|
||||
try recreate(&swapchain, engine);
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Swapchain) void {
|
||||
const allocator = self.engine.vk_allocator.allocator;
|
||||
pub fn deinit(self: *Swapchain, engine: *Engine) void {
|
||||
const allocator = engine.vk_allocator.allocator;
|
||||
|
||||
for (self.swapchain_images) |swapchain_image| {
|
||||
swapchain_image.deinit(self.engine);
|
||||
swapchain_image.deinit(engine);
|
||||
}
|
||||
allocator.free(self.swapchain_images);
|
||||
|
||||
if (self.swapchain != .null_handle) {
|
||||
self.engine.device.destroySwapchainKHR(self.swapchain, &self.engine.vk_allocator.interface);
|
||||
engine.device.destroySwapchainKHR(self.swapchain, &engine.vk_allocator.interface);
|
||||
}
|
||||
|
||||
self.engine.device.destroyRenderPass(self.render_pass, &self.engine.vk_allocator.interface);
|
||||
engine.device.destroyRenderPass(self.render_pass, &engine.vk_allocator.interface);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn recreate(self: *Swapchain) !void {
|
||||
const mode = &self.engine.mode.surface;
|
||||
const allocator = self.engine.vk_allocator.allocator;
|
||||
pub fn recreate(self: *Swapchain, engine: *Engine) !void {
|
||||
const mode = &engine.mode.surface;
|
||||
const allocator = engine.vk_allocator.allocator;
|
||||
|
||||
const old_swapchain = self.swapchain;
|
||||
const old_swapchain_images = self.swapchain_images;
|
||||
const old_semaphore_image_acquired = self.semaphore_image_acquired;
|
||||
|
||||
const extent = try getCurrentExtent(self.engine);
|
||||
const extent = try getCurrentExtent(engine);
|
||||
|
||||
std.log.debug("Recreating swapchain with extent of {d}×{d}...", .{ extent.width, extent.height });
|
||||
|
||||
// --- CREATE NEW SWAPCHAIN ------------------------------------------------
|
||||
|
||||
const surface_capabilities = try self.engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.engine.physical_device, mode.surface);
|
||||
const surface_capabilities = try engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(engine.physical_device, mode.surface);
|
||||
|
||||
const qsm = QSM.resolve(self.engine.graphics_queue.allocation.family, mode.presentation_queue.allocation.family);
|
||||
const new_swapchain = try self.engine.device.createSwapchainKHR(&.{
|
||||
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, mode.presentation_queue.allocation.family);
|
||||
const new_swapchain = try engine.device.createSwapchainKHR(&.{
|
||||
.surface = mode.surface,
|
||||
.min_image_count = self.params.image_count,
|
||||
.image_format = self.params.surface_format.format,
|
||||
@@ -110,8 +120,8 @@ pub fn recreate(self: *Swapchain) !void {
|
||||
.present_mode = .fifo_khr,
|
||||
.clipped = .true,
|
||||
.old_swapchain = old_swapchain,
|
||||
}, &self.engine.vk_allocator.interface);
|
||||
errdefer self.engine.device.destroySwapchainKHR(new_swapchain, &self.engine.vk_allocator.interface);
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroySwapchainKHR(new_swapchain, &engine.vk_allocator.interface);
|
||||
|
||||
// --- DESTROY OLD SWAPCHAIN AND ITS IMAGES --------------------------------
|
||||
|
||||
@@ -120,29 +130,36 @@ pub fn recreate(self: *Swapchain) !void {
|
||||
// null and deinit the old swapchain and images.
|
||||
|
||||
for (old_swapchain_images) |swapchain_image| {
|
||||
swapchain_image.deinit(self.engine);
|
||||
swapchain_image.deinit(engine);
|
||||
}
|
||||
allocator.free(self.swapchain_images);
|
||||
self.swapchain_images = &.{};
|
||||
|
||||
if (old_swapchain != .null_handle) {
|
||||
self.engine.device.destroySwapchainKHR(old_swapchain, &self.engine.vk_allocator.interface);
|
||||
engine.device.destroySwapchainKHR(old_swapchain, &engine.vk_allocator.interface);
|
||||
self.swapchain = .null_handle;
|
||||
}
|
||||
|
||||
self.extent = .{ .width = 0, .height = 0 };
|
||||
|
||||
if (old_semaphore_image_acquired != .null_handle) {
|
||||
engine.destroySemaphore(old_semaphore_image_acquired);
|
||||
self.semaphore_image_acquired = .null_handle;
|
||||
}
|
||||
|
||||
// --- CREATE NEW SWAPCHAIN IMAGES -----------------------------------------
|
||||
|
||||
const new_swapchain_images = blk: {
|
||||
const images = try self.engine.device.getSwapchainImagesAllocKHR(new_swapchain, allocator);
|
||||
const images = try engine.device.getSwapchainImagesAllocKHR(new_swapchain, allocator);
|
||||
defer allocator.free(images);
|
||||
|
||||
var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len);
|
||||
errdefer swapchain_images.deinit(allocator);
|
||||
|
||||
errdefer for (swapchain_images.items) |x| x.deinit(self.engine);
|
||||
errdefer for (swapchain_images.items) |x| x.deinit(engine);
|
||||
|
||||
for (images) |image| {
|
||||
swapchain_images.appendAssumeCapacity(try SwapchainImage.init(self.engine, .{
|
||||
swapchain_images.appendAssumeCapacity(try SwapchainImage.init(engine, .{
|
||||
.image = image,
|
||||
.format = self.params.surface_format.format,
|
||||
.render_pass = self.render_pass,
|
||||
@@ -154,20 +171,105 @@ pub fn recreate(self: *Swapchain) !void {
|
||||
};
|
||||
errdefer {
|
||||
for (new_swapchain_images) |swapchain_image| {
|
||||
swapchain_image.deinit(self.engine);
|
||||
swapchain_image.deinit(engine);
|
||||
}
|
||||
allocator.free(new_swapchain_images);
|
||||
}
|
||||
|
||||
// --- ACQUIRE NEXT IMAGE --------------------------------------------------
|
||||
|
||||
var semaphore_image_acquired = try engine.createSemaphore();
|
||||
errdefer engine.destroySemaphore(semaphore_image_acquired);
|
||||
|
||||
const res = try engine.device.acquireNextImageKHR(new_swapchain, std.math.maxInt(u64), semaphore_image_acquired, .null_handle);
|
||||
if (res.result == .not_ready or res.result == .timeout) {
|
||||
return error.ImageAcquireFailed;
|
||||
}
|
||||
|
||||
std.mem.swap(vk.Semaphore, &new_swapchain_images[res.image_index].semaphore_image_acquired, &semaphore_image_acquired);
|
||||
|
||||
// --- COMMIT --------------------------------------------------------------
|
||||
|
||||
self.extent = extent;
|
||||
self.swapchain = new_swapchain;
|
||||
self.swapchain_images = new_swapchain_images;
|
||||
self.image_index = res.image_index;
|
||||
self.semaphore_image_acquired = semaphore_image_acquired;
|
||||
|
||||
std.log.debug("Swapchain recreated.", .{});
|
||||
}
|
||||
|
||||
fn getCurrentExtent(self: *Engine) !vk.Extent2D {
|
||||
const mode = &self.mode.surface;
|
||||
const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, mode.surface);
|
||||
pub fn present(self: *Swapchain, engine: *Engine, command_buffer: vk.CommandBuffer) !PresentResult {
|
||||
const device = engine.device;
|
||||
const mode = &engine.mode.surface;
|
||||
|
||||
std.log.debug("Presenting command buffer {X}.", .{@intFromEnum(command_buffer)});
|
||||
|
||||
// --- WAIT FOR CURRENT FRAME TO FINISH ------------------------------------
|
||||
|
||||
const current_swapchain_image = &self.swapchain_images[self.image_index];
|
||||
try engine.waitForFence(current_swapchain_image.fence);
|
||||
if (current_swapchain_image.command_buffer != .null_handle) {
|
||||
device.freeCommandBuffers(engine.graphics_command_pool, 1, @ptrCast(¤t_swapchain_image.command_buffer));
|
||||
current_swapchain_image.command_buffer = .null_handle;
|
||||
}
|
||||
try engine.resetFence(current_swapchain_image.fence);
|
||||
|
||||
// --- SUBMIT COMMAND BUFFER -----------------------------------------------
|
||||
|
||||
const wait_semaphores = [_]vk.Semaphore{
|
||||
current_swapchain_image.semaphore_image_acquired,
|
||||
};
|
||||
|
||||
const pipeline_stages_flags = [_]vk.PipelineStageFlags{
|
||||
.{ .top_of_pipe_bit = true },
|
||||
};
|
||||
|
||||
std.debug.assert(wait_semaphores.len == pipeline_stages_flags.len);
|
||||
|
||||
const signal_semaphores = [_]vk.Semaphore{
|
||||
current_swapchain_image.semaphore_render_finished,
|
||||
};
|
||||
|
||||
current_swapchain_image.command_buffer = command_buffer;
|
||||
try device.queueSubmit(engine.graphics_queue.handle, 1, &.{
|
||||
.{
|
||||
.wait_semaphore_count = wait_semaphores.len,
|
||||
.p_wait_semaphores = &wait_semaphores,
|
||||
.p_wait_dst_stage_mask = &pipeline_stages_flags,
|
||||
.command_buffer_count = 1,
|
||||
.p_command_buffers = @ptrCast(&command_buffer),
|
||||
.signal_semaphore_count = signal_semaphores.len,
|
||||
.p_signal_semaphores = &signal_semaphores,
|
||||
},
|
||||
}, current_swapchain_image.fence);
|
||||
|
||||
// --- PRESENT CURRENT FRAME -----------------------------------------------
|
||||
|
||||
_ = try device.queuePresentKHR(mode.presentation_queue.handle, &.{
|
||||
.wait_semaphore_count = 1,
|
||||
.p_wait_semaphores = @ptrCast(¤t_swapchain_image.semaphore_render_finished),
|
||||
.swapchain_count = 1,
|
||||
.p_swapchains = @ptrCast(&self.swapchain),
|
||||
.p_image_indices = @ptrCast(&self.image_index),
|
||||
});
|
||||
|
||||
// --- ACQUIRE NEXT FRAME --------------------------------------------------
|
||||
|
||||
const res = try device.acquireNextImageKHR(self.swapchain, std.math.maxInt(u64), self.semaphore_image_acquired, .null_handle);
|
||||
std.mem.swap(vk.Semaphore, &self.swapchain_images[res.image_index].semaphore_image_acquired, &self.semaphore_image_acquired);
|
||||
self.image_index = res.image_index;
|
||||
|
||||
return switch (res.result) {
|
||||
.success => .optimal,
|
||||
.suboptimal_khr => .suboptimal,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn getCurrentExtent(engine: *Engine) !vk.Extent2D {
|
||||
const mode = &engine.mode.surface;
|
||||
const surface_capabilities = try engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(engine.physical_device, mode.surface);
|
||||
|
||||
if (surface_capabilities.current_extent.width != std.math.maxInt(u32) and surface_capabilities.current_extent.height != std.math.maxInt(u32)) {
|
||||
return surface_capabilities.current_extent;
|
||||
@@ -223,6 +325,8 @@ const Params = struct {
|
||||
if (surface_capabilities.max_image_count > 0) surface_capabilities.max_image_count else std.math.maxInt(u32),
|
||||
);
|
||||
|
||||
std.log.debug("Using surface format \"{s}\" and color space \"{s}\" with {d} images.", .{ @tagName(surface_format.format), @tagName(surface_format.color_space), image_count });
|
||||
|
||||
return .{
|
||||
.surface_format = surface_format,
|
||||
.image_count = image_count,
|
||||
@@ -237,6 +341,7 @@ const SwapchainImage = struct {
|
||||
semaphore_render_finished: vk.Semaphore,
|
||||
fence: vk.Fence,
|
||||
framebuffer: vk.Framebuffer,
|
||||
command_buffer: vk.CommandBuffer,
|
||||
|
||||
const InitProps = struct {
|
||||
image: vk.Image,
|
||||
@@ -246,16 +351,10 @@ const SwapchainImage = struct {
|
||||
};
|
||||
|
||||
fn init(engine: *Engine, props: InitProps) !SwapchainImage {
|
||||
const image_view = try engine.device.createImageView(&.{
|
||||
const image_view = try engine.createImageView(.{
|
||||
.image = props.image,
|
||||
.view_type = .@"2d",
|
||||
.format = props.format,
|
||||
.components = .{
|
||||
.r = .identity,
|
||||
.g = .identity,
|
||||
.b = .identity,
|
||||
.a = .identity,
|
||||
},
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
@@ -263,31 +362,28 @@ const SwapchainImage = struct {
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface);
|
||||
});
|
||||
errdefer engine.destroyImageView(image_view);
|
||||
|
||||
const semaphore_image_acquired = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroySemaphore(semaphore_image_acquired, &engine.vk_allocator.interface);
|
||||
const semaphore_image_acquired = try engine.createSemaphore();
|
||||
errdefer engine.destroySemaphore(semaphore_image_acquired);
|
||||
|
||||
const semaphore_render_finished = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroySemaphore(semaphore_render_finished, &engine.vk_allocator.interface);
|
||||
const semaphore_render_finished = try engine.createSemaphore();
|
||||
errdefer engine.destroySemaphore(semaphore_render_finished);
|
||||
|
||||
const fence = try engine.device.createFence(&.{
|
||||
const fence = try engine.createFence(.{
|
||||
.flags = .{ .signaled_bit = true },
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||
});
|
||||
errdefer engine.destroyFence(fence);
|
||||
|
||||
const attachments = [_]vk.ImageView{image_view};
|
||||
|
||||
const framebuffer = try engine.device.createFramebuffer(&.{
|
||||
const framebuffer = try engine.createFramebuffer(.{
|
||||
.render_pass = props.render_pass,
|
||||
.attachment_count = attachments.len,
|
||||
.p_attachments = &attachments,
|
||||
.attachments = &.{image_view},
|
||||
.width = props.extent.width,
|
||||
.height = props.extent.height,
|
||||
.layers = 1,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyFramebuffer(framebuffer, &engine.vk_allocator.interface);
|
||||
});
|
||||
errdefer engine.destroyFramebuffer(framebuffer);
|
||||
|
||||
return .{
|
||||
.image = props.image,
|
||||
@@ -296,20 +392,16 @@ const SwapchainImage = struct {
|
||||
.semaphore_render_finished = semaphore_render_finished,
|
||||
.fence = fence,
|
||||
.framebuffer = framebuffer,
|
||||
.command_buffer = .null_handle,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: SwapchainImage, engine: *Engine) void {
|
||||
self.waitForFence(engine) catch return;
|
||||
engine.device.destroyFramebuffer(self.framebuffer, &engine.vk_allocator.interface);
|
||||
engine.device.destroyFence(self.fence, &engine.vk_allocator.interface);
|
||||
engine.device.destroySemaphore(self.semaphore_render_finished, &engine.vk_allocator.interface);
|
||||
engine.device.destroySemaphore(self.semaphore_image_acquired, &engine.vk_allocator.interface);
|
||||
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface);
|
||||
}
|
||||
|
||||
fn waitForFence(self: SwapchainImage, engine: *Engine) !void {
|
||||
const fences = [_]vk.Fence{self.fence};
|
||||
_ = try engine.device.waitForFences(fences.len, &fences, .true, std.math.maxInt(u64));
|
||||
engine.waitForFence(self.fence) catch {};
|
||||
engine.destroyFramebuffer(self.framebuffer);
|
||||
engine.destroyFence(self.fence);
|
||||
engine.destroySemaphore(self.semaphore_render_finished);
|
||||
engine.destroySemaphore(self.semaphore_image_acquired);
|
||||
engine.destroyImageView(self.image_view);
|
||||
}
|
||||
};
|
||||
|
||||
4
src/engine/TargetQueue.zig
Normal file
4
src/engine/TargetQueue.zig
Normal file
@@ -0,0 +1,4 @@
|
||||
pub const TargetQueue = enum {
|
||||
graphics,
|
||||
compute,
|
||||
};
|
||||
@@ -5,7 +5,7 @@ const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
const QSM = @import("QueueSharingMode.zig");
|
||||
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
|
||||
|
||||
pub const Usage = enum {
|
||||
base_color,
|
||||
@@ -13,7 +13,7 @@ pub const Usage = enum {
|
||||
occlusion_roughness_metallic,
|
||||
emissive,
|
||||
|
||||
pub fn format(self: Usage) vk.Format {
|
||||
pub fn vkFormat(self: Usage) vk.Format {
|
||||
return switch (self) {
|
||||
.base_color => .r8g8b8a8_srgb,
|
||||
.normal => .r8g8b8a8_snorm,
|
||||
@@ -22,7 +22,7 @@ pub const Usage = enum {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sampleCount(self: Usage) u32 {
|
||||
pub fn samplesPerTexel(self: Usage) u32 {
|
||||
return switch (self) {
|
||||
.base_color => 4,
|
||||
.normal => 4,
|
||||
@@ -34,47 +34,58 @@ pub const Usage = enum {
|
||||
pub fn SampleType(comptime self: Usage) type {
|
||||
return switch (self) {
|
||||
.base_color => u8,
|
||||
.normal => u8,
|
||||
.normal => i8,
|
||||
.occlusion_roughness_metallic => u8,
|
||||
.emissive => f16,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sampleSize(self: Usage) u32 {
|
||||
pub fn bytesPerSample(self: Usage) u32 {
|
||||
return switch (self) {
|
||||
inline else => |x| @sizeOf(SampleType(x)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn TexelType(comptime self: Usage) type {
|
||||
return [self.sampleCount()]SampleType(self);
|
||||
return [self.samplesPerTexel()]SampleType(self);
|
||||
}
|
||||
|
||||
pub fn texelSize(self: Usage) u32 {
|
||||
pub fn bytesPerTexel(self: Usage) u32 {
|
||||
return switch (self) {
|
||||
inline else => |x| @sizeOf(TexelType(x)),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const InitInfo = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
usage: Usage,
|
||||
target_queue: TargetQueue,
|
||||
};
|
||||
|
||||
image: vk.Image,
|
||||
image_view: vk.ImageView,
|
||||
memory: vk.DeviceMemory,
|
||||
device_memory: vk.DeviceMemory,
|
||||
target_queue: TargetQueue,
|
||||
|
||||
width: u32,
|
||||
height: u32,
|
||||
usage: Usage,
|
||||
|
||||
pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture {
|
||||
const format: vk.Format = usage.format();
|
||||
pub fn init(engine: *Engine, init_info: InitInfo) !Texture {
|
||||
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 qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
|
||||
const image = try engine.device.createImage(&.{
|
||||
const image = try engine.createImage(.{
|
||||
.image_type = .@"2d",
|
||||
.format = format,
|
||||
.format = init_info.usage.vkFormat(),
|
||||
.extent = .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.width = init_info.width,
|
||||
.height = init_info.height,
|
||||
.depth = 1,
|
||||
},
|
||||
.mip_levels = 1,
|
||||
@@ -85,29 +96,21 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture {
|
||||
.transfer_dst_bit = true,
|
||||
.sampled_bit = true,
|
||||
},
|
||||
.sharing_mode = qsm.sharing_mode,
|
||||
.queue_family_index_count = qsm.queue_family_index_count,
|
||||
.p_queue_family_indices = qsm.p_queue_family_indices,
|
||||
.queue_family_indices = &.{ target_queue_family, transfer_queue_family },
|
||||
.initial_layout = .undefined,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyImage(image, &engine.vk_allocator.interface);
|
||||
});
|
||||
errdefer engine.destroyImage(image);
|
||||
|
||||
const memory_requirements = engine.device.getImageMemoryRequirements(image);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.freeMemory(device_memory);
|
||||
|
||||
try engine.device.bindImageMemory(image, memory, 0);
|
||||
try engine.device.bindImageMemory(image, device_memory, 0);
|
||||
|
||||
const image_view = try engine.device.createImageView(&.{
|
||||
const image_view = try engine.createImageView(.{
|
||||
.image = image,
|
||||
.view_type = .@"2d",
|
||||
.format = format,
|
||||
.components = .{
|
||||
.r = .identity,
|
||||
.g = .identity,
|
||||
.b = .identity,
|
||||
.a = .identity,
|
||||
},
|
||||
.format = init_info.usage.vkFormat(),
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
@@ -115,49 +118,72 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture {
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface);
|
||||
});
|
||||
errdefer engine.destroyImageView(image_view);
|
||||
|
||||
return .{
|
||||
.image = image,
|
||||
.image_view = image_view,
|
||||
.memory = memory,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.usage = usage,
|
||||
.device_memory = device_memory,
|
||||
.target_queue = init_info.target_queue,
|
||||
.width = init_info.width,
|
||||
.height = init_info.height,
|
||||
.usage = init_info.usage,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Texture, engine: *Engine) void {
|
||||
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface);
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyImage(self.image, &engine.vk_allocator.interface);
|
||||
engine.destroyImageView(self.image_view);
|
||||
engine.freeMemory(self.device_memory);
|
||||
engine.destroyImage(self.image);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T) !void {
|
||||
const bytes_per_texel = self.usage.texelSize();
|
||||
const bytes_per_row = self.width * bytes_per_texel;
|
||||
const byte_length = @as(usize, self.height) * bytes_per_row;
|
||||
pub fn writeTexels(self: Texture, comptime TexelType: type, engine: *Engine, texels: []const TexelType) !void {
|
||||
const texel_count = try std.math.mul(u32, self.width, self.height);
|
||||
std.debug.assert(texels.len == texel_count);
|
||||
switch (self.usage) {
|
||||
inline else => |x| std.debug.assert(TexelType == x.TexelType()),
|
||||
}
|
||||
|
||||
std.debug.assert(data.len * @sizeOf(T) == byte_length);
|
||||
try self.writeRaw(engine, std.mem.sliceAsBytes(texels));
|
||||
}
|
||||
|
||||
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
|
||||
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||
pub fn writeSamples(self: Texture, comptime SampleType: type, engine: *Engine, samples: []const SampleType) !void {
|
||||
const texel_count = try std.math.mul(u32, self.width, self.height);
|
||||
const sample_count = try std.math.mul(u32, texel_count, self.usage.samplesPerTexel());
|
||||
std.debug.assert(samples.len == sample_count);
|
||||
switch (self.usage) {
|
||||
inline else => |x| std.debug.assert(SampleType == x.SampleType()),
|
||||
}
|
||||
|
||||
var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(data), engine.graphics_queue.allocation.family);
|
||||
try self.writeRaw(engine, std.mem.sliceAsBytes(samples));
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void {
|
||||
const texel_count = try std.math.mul(u32, self.width, self.height);
|
||||
const byte_length = try std.math.mul(u32, texel_count, self.usage.bytesPerTexel());
|
||||
std.debug.assert(data.len == byte_length);
|
||||
|
||||
var staging_buffer = try StagingBuffer.init(engine, .{
|
||||
.capacity = @intCast(byte_length),
|
||||
.target_queue = self.target_queue,
|
||||
});
|
||||
defer staging_buffer.deinit(engine);
|
||||
|
||||
const staging_memory = try staging_buffer.map(engine);
|
||||
@memcpy(staging_memory, data);
|
||||
staging_buffer.unmap(engine);
|
||||
|
||||
// --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY -----------------
|
||||
|
||||
const transfer_command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(transfer_command_buffer);
|
||||
|
||||
const graphics_command_buffer = try engine.allocateGraphicsCommandBuffer();
|
||||
defer engine.freeGraphicsCommandBuffer(graphics_command_buffer);
|
||||
|
||||
try transfer_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
const pre_copy_barriers = [_]vk.ImageMemoryBarrier{
|
||||
const transfer_transition_barriers = [_]vk.ImageMemoryBarrier{
|
||||
.{
|
||||
.src_access_mask = .{},
|
||||
.dst_access_mask = .{ .transfer_write_bit = true },
|
||||
@@ -184,8 +210,8 @@ pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T)
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
pre_copy_barriers.len,
|
||||
&pre_copy_barriers,
|
||||
transfer_transition_barriers.len,
|
||||
&transfer_transition_barriers,
|
||||
);
|
||||
|
||||
const regions = [_]vk.BufferImageCopy{
|
||||
@@ -213,14 +239,28 @@ pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T)
|
||||
);
|
||||
|
||||
try transfer_command_buffer.endCommandBuffer();
|
||||
try engine.submitTransferCommandBuffer(transfer_command_buffer, fence);
|
||||
|
||||
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
try engine.device.resetFences(1, @ptrCast(&fence));
|
||||
const semaphore = try engine.createSemaphore();
|
||||
defer engine.destroySemaphore(semaphore);
|
||||
|
||||
const transfer_submits = [_]vk.SubmitInfo{
|
||||
.{
|
||||
.command_buffer_count = 1,
|
||||
.p_command_buffers = @ptrCast(&transfer_command_buffer.handle),
|
||||
.signal_semaphore_count = 1,
|
||||
.p_signal_semaphores = @ptrCast(&semaphore),
|
||||
},
|
||||
};
|
||||
try engine.device.queueSubmit(engine.transfer_queue.handle, transfer_submits.len, &transfer_submits, .null_handle);
|
||||
|
||||
// --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ----------------------
|
||||
|
||||
const graphics_command_buffer = try engine.allocateGraphicsCommandBuffer();
|
||||
defer engine.freeGraphicsCommandBuffer(graphics_command_buffer);
|
||||
|
||||
try graphics_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
const post_copy_barriers = [_]vk.ImageMemoryBarrier{
|
||||
const graphics_transition_barriers = [_]vk.ImageMemoryBarrier{
|
||||
.{
|
||||
.src_access_mask = .{ .transfer_write_bit = true },
|
||||
.dst_access_mask = .{ .shader_read_bit = true },
|
||||
@@ -247,12 +287,29 @@ pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T)
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
post_copy_barriers.len,
|
||||
&post_copy_barriers,
|
||||
graphics_transition_barriers.len,
|
||||
&graphics_transition_barriers,
|
||||
);
|
||||
|
||||
try graphics_command_buffer.endCommandBuffer();
|
||||
try engine.submitGraphicsCommandBuffer(graphics_command_buffer, fence);
|
||||
|
||||
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
const wait_stage_masks = [_]vk.PipelineStageFlags{
|
||||
.{ .top_of_pipe_bit = true },
|
||||
};
|
||||
|
||||
const graphics_submits = [_]vk.SubmitInfo{
|
||||
.{
|
||||
.command_buffer_count = 1,
|
||||
.p_command_buffers = @ptrCast(&graphics_command_buffer.handle),
|
||||
.wait_semaphore_count = 1,
|
||||
.p_wait_semaphores = @ptrCast(&semaphore),
|
||||
.p_wait_dst_stage_mask = &wait_stage_masks,
|
||||
},
|
||||
};
|
||||
|
||||
const fence = try engine.createFence(.{});
|
||||
defer engine.destroyFence(fence);
|
||||
|
||||
try engine.device.queueSubmit(engine.graphics_queue.handle, graphics_submits.len, &graphics_submits, fence);
|
||||
try engine.waitForFence(fence);
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
const VertexBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
const QSM = @import("QueueSharingMode.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
vertex_size: usize,
|
||||
vertex_count: usize,
|
||||
|
||||
pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !VertexBuffer {
|
||||
const vertex_size = @sizeOf(VertexType);
|
||||
const size = std.math.mul(usize, vertex_count, vertex_size) catch return error.OutOfMemory;
|
||||
|
||||
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = size,
|
||||
.usage = .{
|
||||
.transfer_dst_bit = true,
|
||||
.vertex_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = qsm.sharing_mode,
|
||||
.queue_family_index_count = qsm.queue_family_index_count,
|
||||
.p_queue_family_indices = qsm.p_queue_family_indices,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.vertex_size = vertex_size,
|
||||
.vertex_count = vertex_count,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *VertexBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: VertexBuffer, comptime VertexType: type, engine: *Engine, vertices: []const VertexType) !void {
|
||||
std.debug.assert(vertices.len == self.vertex_count);
|
||||
std.debug.assert(@sizeOf(VertexType) == self.vertex_size);
|
||||
|
||||
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
|
||||
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||
|
||||
const size = std.mem.sliceAsBytes(vertices).len;
|
||||
|
||||
var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(vertices), engine.graphics_queue.allocation.family);
|
||||
defer staging_buffer.deinit(engine);
|
||||
|
||||
const command_buffer = try engine.allocateTransferCommandBuffer();
|
||||
defer engine.freeTransferCommandBuffer(command_buffer);
|
||||
|
||||
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
const regions = [_]vk.BufferCopy{
|
||||
.{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = size,
|
||||
},
|
||||
};
|
||||
|
||||
command_buffer.copyBuffer(
|
||||
staging_buffer.buffer,
|
||||
self.buffer,
|
||||
regions.len,
|
||||
®ions,
|
||||
);
|
||||
|
||||
try command_buffer.endCommandBuffer();
|
||||
try engine.submitTransferCommandBuffer(command_buffer, fence);
|
||||
|
||||
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
|
||||
}
|
||||
75
src/engine/atoms.zig
Normal file
75
src/engine/atoms.zig
Normal file
@@ -0,0 +1,75 @@
|
||||
pub const Atoms = @This();
|
||||
const std = @import("std");
|
||||
|
||||
var allocator: std.mem.Allocator = undefined;
|
||||
var string_arena: std.heap.ArenaAllocator = undefined;
|
||||
var map: Map = undefined;
|
||||
var array: Array = undefined;
|
||||
var mutex: std.Thread.Mutex = undefined;
|
||||
|
||||
pub const Atom = enum(u32) { _ };
|
||||
|
||||
pub const Map = std.StringHashMapUnmanaged(Atom);
|
||||
pub const Array = std.ArrayList([:0]const u8);
|
||||
|
||||
pub fn init(_allocator: std.mem.Allocator) void {
|
||||
allocator = _allocator;
|
||||
string_arena = .init(_allocator);
|
||||
map = .{};
|
||||
array = .empty;
|
||||
mutex = .{};
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
string_arena.deinit();
|
||||
map.deinit(allocator);
|
||||
array.deinit(allocator);
|
||||
|
||||
allocator = undefined;
|
||||
string_arena = undefined;
|
||||
map = undefined;
|
||||
array = undefined;
|
||||
mutex = undefined;
|
||||
}
|
||||
|
||||
pub fn getString(atom: Atom) [:0]const u8 {
|
||||
try mutex.lock();
|
||||
defer mutex.unlock();
|
||||
|
||||
return array.items[atom.value];
|
||||
}
|
||||
|
||||
pub fn getAtom(string: []const u8) ?Atom {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
|
||||
return map.get(string);
|
||||
}
|
||||
|
||||
pub fn getOrPutAtom(string: []const u8) !Atom {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
|
||||
const entry = try map.getOrPut(allocator, string);
|
||||
|
||||
if (entry.found_existing) {
|
||||
return entry.value_ptr.*;
|
||||
} else {
|
||||
errdefer _ = map.remove(string);
|
||||
try array.ensureUnusedCapacity(allocator, 1);
|
||||
|
||||
const owned_string = try toOwnedString(string);
|
||||
const atom: Atom = @enumFromInt(array.items.len);
|
||||
|
||||
entry.key_ptr.* = owned_string;
|
||||
entry.value_ptr.* = atom;
|
||||
|
||||
array.appendAssumeCapacity(owned_string);
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
|
||||
fn toOwnedString(string: []const u8) ![:0]const u8 {
|
||||
const owned_string = try string_arena.allocator().dupeZ(u8, string);
|
||||
return owned_string;
|
||||
}
|
||||
Reference in New Issue
Block a user