From 9f2d1e46088e9c9ceb5f69324d82493dee2f0f46 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Wed, 26 Nov 2025 01:19:20 +0100 Subject: [PATCH] Refactor literally everything --- assets/shaders/main.frag | 23 +- assets/shaders/main.vert | 21 +- assets/shaders/main_common.glsl | 27 +- src/Game.zig | 917 ++++++++++++++++++++++---------- src/assets/Atoms.zig | 70 --- src/assets/Materials.zig | 157 +++--- src/assets/Textures.zig | 170 +++--- src/engine/Engine.zig | 549 +++++++++++++++++++ src/engine/GenericBuffer.zig | 210 ++++++++ src/engine/IndexBuffer.zig | 85 --- src/engine/StagingBuffer.zig | 60 ++- src/engine/StorageBuffer.zig | 200 ------- src/engine/Swapchain.zig | 214 +++++--- src/engine/TargetQueue.zig | 4 + src/engine/Texture.zig | 181 ++++--- src/engine/VertexBuffer.zig | 89 ---- src/engine/atoms.zig | 75 +++ src/main.zig | 8 +- src/math/Vector2.zig | 7 + src/math/Vector3.zig | 7 + src/math/Vector4.zig | 7 + src/voxel.zig | 23 + 22 files changed, 2070 insertions(+), 1034 deletions(-) delete mode 100644 src/assets/Atoms.zig create mode 100644 src/engine/GenericBuffer.zig delete mode 100644 src/engine/IndexBuffer.zig delete mode 100644 src/engine/StorageBuffer.zig create mode 100644 src/engine/TargetQueue.zig delete mode 100644 src/engine/VertexBuffer.zig create mode 100644 src/engine/atoms.zig create mode 100644 src/voxel.zig diff --git a/assets/shaders/main.frag b/assets/shaders/main.frag index 7f2e68a..ebaf365 100644 --- a/assets/shaders/main.frag +++ b/assets/shaders/main.frag @@ -1,13 +1,15 @@ #version 460 #extension GL_EXT_nonuniform_qualifier : require #extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require in Varyings { - layout(location = 0) vec3 positionVS; - layout(location = 1) vec2 texCoord; - layout(location = 2) vec3 normalVS; - layout(location = 3) vec3 tangentVS; - layout(location = 4) vec3 bitangentVS; + layout(location = 0) flat uint instance; + layout(location = 1) vec3 positionVS; + layout(location = 2) vec2 texCoord; + layout(location = 3) vec3 normalVS; + layout(location = 4) vec3 tangentVS; + layout(location = 5) vec3 bitangentVS; } var; #include "main_common.glsl" @@ -78,13 +80,14 @@ vec4 texture2DAA(texture2D tex, vec2 texCoord) { return texture(sampler2D(tex, _Sampler), texCoord); } -#define MATERIAL _Materials[_Object.material] +#define OBJECT _Object[var.instance] +#define MATERIAL _Materials[uint(OBJECT.material)] void main() { - vec4 baseColorTexel = texture2DAA(_Textures[MATERIAL.baseColorTexture], var.texCoord); - vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[MATERIAL.occlusionRoughnessMetallicTexture], var.texCoord); - vec4 normalTexel = texture2DAA(_Textures[MATERIAL.normalTexture], var.texCoord); - vec4 emissiveTexel = texture2DAA(_Textures[MATERIAL.emissiveTexture], var.texCoord); + vec4 baseColorTexel = texture2DAA(_Textures[uint(MATERIAL.baseColorTexture)], var.texCoord); + vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[uint(MATERIAL.occlusionRoughnessMetallicTexture)], var.texCoord); + vec4 normalTexel = texture2DAA(_Textures[uint(MATERIAL.normalTexture)], var.texCoord); + vec4 emissiveTexel = texture2DAA(_Textures[uint(MATERIAL.emissiveTexture)], var.texCoord); vec3 baseColor = MATERIAL.baseColor * baseColorTexel.rgb; float occlusion = 1.0 + MATERIAL.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0); diff --git a/assets/shaders/main.vert b/assets/shaders/main.vert index cc6bb41..918e8e1 100644 --- a/assets/shaders/main.vert +++ b/assets/shaders/main.vert @@ -1,6 +1,7 @@ #version 460 #extension GL_EXT_nonuniform_qualifier : require #extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_16bit_storage : require layout(location = 0) in vec3 positionOS; layout(location = 1) in vec2 texCoord; @@ -8,29 +9,33 @@ layout(location = 2) in vec3 normalOS; layout(location = 3) in vec4 tangentOS; out Varyings { - layout(location = 0) vec3 positionVS; - layout(location = 1) vec2 texCoord; - layout(location = 2) vec3 normalVS; - layout(location = 3) vec3 tangentVS; - layout(location = 4) vec3 bitangentVS; + layout(location = 0) flat uint instance; + layout(location = 1) vec3 positionVS; + layout(location = 2) vec2 texCoord; + layout(location = 3) vec3 normalVS; + layout(location = 4) vec3 tangentVS; + layout(location = 5) vec3 bitangentVS; } var; #include "main_common.glsl" +#define OBJECT _Object[gl_InstanceIndex] + void main() { - vec3 positionWS = (_Object.matrixOStoWS * vec4(positionOS, 1.0)).xyz; + vec3 positionWS = (OBJECT.matrixOStoWS * vec4(positionOS, 1.0)).xyz; vec3 positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz; vec4 positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0); - vec3 normalWS = normalize((_Object.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz); + vec3 normalWS = normalize((OBJECT.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz); vec3 normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz); - vec3 tangentWS = normalize((_Object.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz); + vec3 tangentWS = normalize((OBJECT.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz); vec3 tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz); vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS)); gl_Position = positionCS; + var.instance = gl_InstanceIndex; var.positionVS = positionVS; var.texCoord = texCoord; var.normalVS = normalVS; diff --git a/assets/shaders/main_common.glsl b/assets/shaders/main_common.glsl index e08df3a..767f674 100644 --- a/assets/shaders/main_common.glsl +++ b/assets/shaders/main_common.glsl @@ -12,16 +12,24 @@ struct DirectionalLight { struct Material { vec3 baseColor; - uint baseColorTexture; vec3 emissive; - uint emissiveTexture; float ior; float metallic; float normalScale; - uint normalTexture; - uint occlusionRoughnessMetallicTexture; float occlusionTextureStrength; float roughness; + + uint16_t baseColorTexture; + uint16_t emissiveTexture; + uint16_t normalTexture; + uint16_t occlusionRoughnessMetallicTexture; +}; + +struct ObjectUniforms { + mat4 matrixOStoWS; + mat4 matrixOStoWSNormal; + + uint16_t material; }; layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { @@ -47,11 +55,8 @@ layout(set = 0, binding = 3, scalar) readonly buffer Materials { layout(set = 0, binding = 4) uniform sampler _Sampler; layout(set = 0, binding = 5) uniform texture2D _Textures[]; -// --- SET 1 --- PER OBJECT ---------------------------------------------------- +// --- SET 1 --- PER BATCH ----------------------------------------------------- -layout(set = 1, binding = 0, scalar) uniform ObjectUniforms { - mat4 matrixOStoWS; - mat4 matrixOStoWSNormal; - - uint material; -} _Object; +layout(set = 1, binding = 0, scalar) readonly buffer ObjectsUniforms { + ObjectUniforms _Object[]; +}; diff --git a/src/Game.zig b/src/Game.zig index 238f20a..aa5c9bd 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -6,38 +6,118 @@ const vk = @import("vulkan"); const math = @import("math.zig"); -const Atoms = @import("assets/Atoms.zig"); -const Textures = @import("assets/Textures.zig"); const Materials = @import("assets/Materials.zig"); +const Textures = @import("assets/Textures.zig"); +const Engine = @import("engine/Engine.zig"); +const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer; const Swapchain = @import("engine/Swapchain.zig"); -const VertexBuffer = @import("engine/VertexBuffer.zig"); -const IndexBuffer = @import("engine/IndexBuffer.zig"); const Iterator3 = math.Iterator3; const Matrix4x4 = math.Matrix4x4; const Quaternion = math.Quaternion; const Vector2 = math.Vector2; const Vector3 = math.Vector3; +const Vector4 = math.Vector4; + +const PointLight = extern struct { + positionWS: [3]f32, + color: [3]f32, + + pub fn init(position_ws: Vector3, color: Vector3) PointLight { + return .{ + .positionWS = position_ws.asArray(), + .color = color.asArray(), + }; + } +}; + +const DirectionalLight = extern struct { + directionWS: [3]f32, + color: [3]f32, + + pub fn init(direction_ws: Vector3, color: Vector3) DirectionalLight { + return .{ + .directionWS = direction_ws.asArray(), + .color = color.asArray(), + }; + } +}; + +const GlobalUniforms = extern struct { + matrixWStoVS: [16]f32, + matrixVStoCS: [16]f32, + ambientLight: [3]f32, + + pub fn init(matrix_ws_to_vs: Matrix4x4, matrix_vs_to_cs: Matrix4x4, ambient_light: Vector3) GlobalUniforms { + return .{ + .matrixWStoVS = matrix_ws_to_vs.asArray(), + .matrixVStoCS = matrix_vs_to_cs.asArray(), + .ambientLight = ambient_light.asArray(), + }; + } +}; + +const ObjectUniforms = extern struct { + matrixOStoWS: [16]f32, + matrixOStoWSNormal: [16]f32, + + material: Materials.Id, + + pub fn init(matrix_os_to_ws: Matrix4x4, matrix_ow_to_ws_normal: Matrix4x4, material: Materials.Id) ObjectUniforms { + return .{ + .matrixOStoWS = matrix_os_to_ws.asArray(), + .matrixOStoWSNormal = matrix_ow_to_ws_normal.asArray(), + .material = material, + }; + } +}; const Vertex = extern struct { positionOS: [3]f32, texCoord: [2]u16, normalOS: [3]i8, tangentOS: [4]i8, + + pub fn init(position_os: Vector3, tex_coord: Vector2, normal_os: Vector3, tangent_os: Vector4) Vertex { + return .{ + .positionOS = position_os.asArray(), + .texCoord = tex_coord.asArrayNorm(u16), + .normalOS = normal_os.asArrayNorm(i8), + .tangentOS = tangent_os.asArrayNorm(i8), + }; + } }; +const GlobalUniformsBuffer = GenericBuffer(GlobalUniforms, void); +const PointLightBuffer = GenericBuffer(u32, PointLight); +const DirectionalLightBuffer = GenericBuffer(u32, DirectionalLight); +const ObjectUniformsBuffer = GenericBuffer(void, ObjectUniforms); + +const VertexBuffer = GenericBuffer(void, Vertex); +const IndexBuffer = GenericBuffer(void, u16); + const main_vert_spv align(4) = @embedFile("shaders/main_vert.spv").*; const main_frag_spv align(4) = @embedFile("shaders/main_frag.spv").*; allocator: std.mem.Allocator, +engine: *Engine, swapchain: *Swapchain, global_descriptor_set_layout: vk.DescriptorSetLayout, -per_object_descriptor_set_layout: vk.DescriptorSetLayout, +per_batch_descriptor_set_layout: vk.DescriptorSetLayout, +descriptor_pool: vk.DescriptorPool, +/// [0] GLOBAL, [1] PER OBJECT +descriptor_sets: [2]vk.DescriptorSet, pipeline_layout: vk.PipelineLayout, pipeline: vk.Pipeline, + vertex_buffer: VertexBuffer, index_buffer: IndexBuffer, -atoms: Atoms, +global_uniforms: GlobalUniformsBuffer, +point_lights: PointLightBuffer, +directional_lights: DirectionalLightBuffer, +object_uniforms: ObjectUniformsBuffer, +sampler: vk.Sampler, + materials: Materials, textures: Textures, @@ -50,323 +130,468 @@ input_backwards: bool = false, input_left: bool = false, input_right: bool = false, +const max_textures = 1024; +const max_point_lights = 1024; +const max_directional_lights = 4; +const max_objects = 1024; + const camera_near_plane = 0.1; const camera_vertical_fov_deg = 90.0; const camera_half_vertical_fov_rad = 0.5 * camera_vertical_fov_deg * std.math.rad_per_deg; const camera_mouse_sensitivity = 0.0015; -const max_point_lights = 100; -const max_directional_lights = 4; - const player_speed = 5.0; -pub fn init(allocator: std.mem.Allocator, swapchain: *Swapchain) !Game { - var atoms = Atoms.init(allocator); - errdefer atoms.deinit(); +pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain) !Game { + var materials = try Materials.init(engine, allocator); + errdefer materials.deinit(engine, allocator); - var materials = try Materials.init(allocator, swapchain.engine); - errdefer materials.deinit(swapchain.engine); + var textures = try Textures.init(engine, allocator); + errdefer textures.deinit(engine, allocator); - var textures = try Textures.init(allocator, swapchain.engine); - errdefer textures.deinit(swapchain.engine); + _ = try materials.getOrLoadFilename(engine, &textures, "Bricks.json", allocator); + _ = try materials.getOrLoadFilename(engine, &textures, "Dirt.json", allocator); + _ = try materials.getOrLoadFilename(engine, &textures, "Gold.json", allocator); + _ = try materials.getOrLoadFilename(engine, &textures, "Stone.json", allocator); - _ = try materials.getOrLoadId(swapchain.engine, &textures, &atoms, try atoms.getOrPutAtom("Bricks.json")); - _ = try materials.getOrLoadId(swapchain.engine, &textures, &atoms, try atoms.getOrPutAtom("Dirt.json")); - _ = try materials.getOrLoadId(swapchain.engine, &textures, &atoms, try atoms.getOrPutAtom("Gold.json")); - _ = try materials.getOrLoadId(swapchain.engine, &textures, &atoms, try atoms.getOrPutAtom("Stone.json")); + const sampler = try engine.createSampler(.{ + .mag_filter = .linear, + .min_filter = .linear, + .mipmap_mode = .linear, + .address_mode_u = .repeat, + .address_mode_v = .repeat, + .address_mode_w = .repeat, + .mip_lod_bias = 0, + .anisotropy_enable = .false, + .max_anisotropy = 0, + .compare_enable = .false, + .compare_op = .always, + .min_lod = 0, + .max_lod = vk.LOD_CLAMP_NONE, + .border_color = .float_transparent_black, + .unnormalized_coordinates = .false, + }); + errdefer engine.destroySampler(sampler); - const device = swapchain.engine.device; - const interface = &swapchain.engine.vk_allocator.interface; - - const global_descriptor_set_layout_bindings = [_]vk.DescriptorSetLayoutBinding{ - .{ - .binding = 0, - .descriptor_type = .uniform_buffer, - .descriptor_count = 1, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + const global_descriptor_set_layout = try engine.createDescriptorSetLayout(.{ + .bindings = &.{ + .{ + .binding = 0, + .descriptor_type = .uniform_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, + .{ + .binding = 1, + .descriptor_type = .storage_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, + .{ + .binding = 2, + .descriptor_type = .storage_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, + .{ + .binding = 3, + .descriptor_type = .storage_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, + .{ + .binding = 4, + .descriptor_type = .sampler, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + .immutable_samplers = &.{sampler}, + }, + .{ + .binding = 5, + .descriptor_type = .sampled_image, + .descriptor_count = max_textures, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + .flags = .{ .variable_descriptor_count_bit = true }, + }, }, - .{ - .binding = 1, - .descriptor_type = .storage_buffer, - .descriptor_count = 1, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, - }, - .{ - .binding = 2, - .descriptor_type = .storage_buffer, - .descriptor_count = 1, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, - }, - .{ - .binding = 3, - .descriptor_type = .storage_buffer, - .descriptor_count = 1, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, - }, - .{ - .binding = 4, - .descriptor_type = .sampler, - .descriptor_count = 1, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, - }, - .{ - .binding = 5, - .descriptor_type = .sampled_image, - .descriptor_count = 1024, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, - }, - }; + }); + errdefer engine.destroyDescriptorSetLayout(global_descriptor_set_layout); - const global_descriptor_set_layout_bindings_flags = [_]vk.DescriptorBindingFlags{ - .{}, - .{}, - .{}, - .{}, - .{}, - .{ .variable_descriptor_count_bit = true }, - }; - - std.debug.assert(global_descriptor_set_layout_bindings.len == global_descriptor_set_layout_bindings_flags.len); - - const global_descriptor_set_layout = try device.createDescriptorSetLayout(&.{ - .p_next = &vk.DescriptorSetLayoutBindingFlagsCreateInfo{ - .binding_count = global_descriptor_set_layout_bindings_flags.len, - .p_binding_flags = &global_descriptor_set_layout_bindings_flags, + const per_batch_descriptor_set_layout = try engine.createDescriptorSetLayout(.{ + .bindings = &.{ + .{ + .binding = 0, + .descriptor_type = .storage_buffer, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + }, }, - .binding_count = global_descriptor_set_layout_bindings.len, - .p_bindings = &global_descriptor_set_layout_bindings, - }, interface); - errdefer device.destroyDescriptorSetLayout(global_descriptor_set_layout, interface); + }); + errdefer engine.destroyDescriptorSetLayout(per_batch_descriptor_set_layout); - const per_object_descriptor_set_layout_bindings = [_]vk.DescriptorSetLayoutBinding{ - .{ - .binding = 0, - .descriptor_type = .uniform_buffer_dynamic, - .descriptor_count = 1, - .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, + const pipeline_layout = try engine.createPipelineLayout(.{ + .set_layouts = &.{ + global_descriptor_set_layout, + per_batch_descriptor_set_layout, }, - }; + }); + errdefer engine.destroyPipelineLayout(pipeline_layout); - const per_object_descriptor_set_layout = try device.createDescriptorSetLayout(&.{ - .binding_count = per_object_descriptor_set_layout_bindings.len, - .p_bindings = &per_object_descriptor_set_layout_bindings, - }, interface); - errdefer device.destroyDescriptorSetLayout(per_object_descriptor_set_layout, interface); + const vertex_shader = try engine.createShaderModule(.{ .code = &main_vert_spv }); + defer engine.destroyShaderModule(vertex_shader); - const descriptor_set_layouts = [_]vk.DescriptorSetLayout{ - global_descriptor_set_layout, - per_object_descriptor_set_layout, - }; + const fragment_shader = try engine.createShaderModule(.{ .code = &main_frag_spv }); + defer engine.destroyShaderModule(fragment_shader); - const pipeline_layout = try device.createPipelineLayout(&.{ - .set_layout_count = descriptor_set_layouts.len, - .p_set_layouts = &descriptor_set_layouts, - }, interface); - errdefer device.destroyPipelineLayout(pipeline_layout, interface); - - const vertex_shader = try device.createShaderModule(&.{ - .code_size = main_vert_spv.len, - .p_code = @ptrCast(&main_vert_spv), - }, interface); - defer device.destroyShaderModule(vertex_shader, interface); - - const fragment_shader = try device.createShaderModule(&.{ - .code_size = main_frag_spv.len, - .p_code = @ptrCast(&main_frag_spv), - }, interface); - defer device.destroyShaderModule(fragment_shader, interface); - - var vertex_buffer: VertexBuffer = try .init(swapchain.engine, Vertex, 4); - errdefer vertex_buffer.deinit(swapchain.engine); - try vertex_buffer.write(Vertex, swapchain.engine, &.{ - .{ - .positionOS = .{ -0.5, -0.5, 0 }, - .texCoord = .{ 0, 65535 }, - .normalOS = .{ 127, 0, 0 }, - .tangentOS = .{ 127, 0, 0, -127 }, - }, - .{ - .positionOS = .{ 0.5, -0.5, 0 }, - .texCoord = .{ 65535, 65535 }, - .normalOS = .{ 127, 0, 0 }, - .tangentOS = .{ 127, 0, 0, -127 }, - }, - .{ - .positionOS = .{ -0.5, 0.5, 0 }, - .texCoord = .{ 0, 0 }, - .normalOS = .{ 127, 0, 0 }, - .tangentOS = .{ 127, 0, 0, -127 }, - }, - .{ - .positionOS = .{ 0.5, 0.5, 0 }, - .texCoord = .{ 65535, 0 }, - .normalOS = .{ 127, 0, 0 }, - .tangentOS = .{ 127, 0, 0, -127 }, + var vertex_buffer = try VertexBuffer.init(engine, .{ + .usage = .vertex, + .target_queue = .graphics, + .array_capacity = 4, + }); + errdefer vertex_buffer.deinit(engine); + try vertex_buffer.write(engine, .{ + .elements = &.{ + .init( + .init(-0.5, -0.5, 0), + .init(0, 1), + .init(1, 0, 0), + .init(1, 0, 0, -1), + ), + .init( + .init(0.5, -0.5, 0), + .init(1, 1), + .init(1, 0, 0), + .init(1, 0, 0, -1), + ), + .init( + .init(-0.5, 0.5, 0), + .init(0, 0), + .init(1, 0, 0), + .init(1, 0, 0, -1), + ), + .init( + .init(0.5, 0.5, 0), + .init(1, 0), + .init(1, 0, 0), + .init(1, 0, 0, -1), + ), }, }); - var index_buffer: IndexBuffer = try .init(swapchain.engine, 6); - errdefer index_buffer.deinit(swapchain.engine); - try index_buffer.write(swapchain.engine, &.{ 0, 1, 2, 2, 1, 3 }); + var index_buffer = try IndexBuffer.init(engine, .{ + .usage = .index, + .target_queue = .graphics, + .array_capacity = 6, + }); + errdefer index_buffer.deinit(engine); + try index_buffer.write(engine, .{ + .elements = &.{ 0, 1, 2, 2, 1, 3 }, + }); - const pipeline_shader_stages = [_]vk.PipelineShaderStageCreateInfo{ - .{ - .stage = .{ .vertex_bit = true }, - .module = vertex_shader, - .p_name = "main", - }, - .{ - .stage = .{ .fragment_bit = true }, - .module = fragment_shader, - .p_name = "main", - }, - }; + var global_uniforms = try GlobalUniformsBuffer.init(engine, .{ + .usage = .uniform, + .target_queue = .graphics, + }); + errdefer global_uniforms.deinit(engine); - const vertex_input_binding_descriptions = [_]vk.VertexInputBindingDescription{ - .{ - .binding = 0, - .stride = @sizeOf(Vertex), - .input_rate = .vertex, - }, - }; + var point_lights = try PointLightBuffer.init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = max_point_lights, + }); + errdefer point_lights.deinit(engine); - const vertex_input_attribute_descriptions = [_]vk.VertexInputAttributeDescription{ - .{ - .location = 0, - .binding = 0, - .format = .r32g32b32_sfloat, - .offset = @offsetOf(Vertex, "positionOS"), - }, - .{ - .location = 1, - .binding = 0, - .format = .r16g16_unorm, - .offset = @offsetOf(Vertex, "texCoord"), - }, - .{ - .location = 2, - .binding = 0, - .format = .r8g8b8_snorm, - .offset = @offsetOf(Vertex, "normalOS"), - }, - .{ - .location = 3, - .binding = 0, - .format = .r8g8b8a8_snorm, - .offset = @offsetOf(Vertex, "tangentOS"), - }, - }; + var directional_lights = try DirectionalLightBuffer.init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = max_directional_lights, + }); + errdefer directional_lights.deinit(engine); - const pipeline_color_blend_attachment_states = [_]vk.PipelineColorBlendAttachmentState{ - .{ - .blend_enable = .false, - .src_color_blend_factor = .one, - .dst_color_blend_factor = .zero, - .color_blend_op = .add, - .src_alpha_blend_factor = .one, - .dst_alpha_blend_factor = .zero, - .alpha_blend_op = .add, - .color_write_mask = .{ - .r_bit = true, - .g_bit = true, - .b_bit = true, - .a_bit = true, + var object_uniforms = try ObjectUniformsBuffer.init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = max_objects, + }); + errdefer object_uniforms.deinit(engine); + + const pipeline = try engine.createGraphicsPipeline(.{ + .stages = &.{ + .{ + .stage = .{ .vertex_bit = true }, + .module = vertex_shader, + .name = "main", + }, + .{ + .stage = .{ .fragment_bit = true }, + .module = fragment_shader, + .name = "main", }, }, - }; - - const dynamic_states = [_]vk.DynamicState{ .viewport, .scissor }; - - var pipeline: vk.Pipeline = undefined; - _ = try device.createGraphicsPipelines(.null_handle, 1, &.{ - .{ - .stage_count = pipeline_shader_stages.len, - .p_stages = &pipeline_shader_stages, - .p_vertex_input_state = &.{ - .vertex_binding_description_count = vertex_input_binding_descriptions.len, - .p_vertex_binding_descriptions = &vertex_input_binding_descriptions, - .vertex_attribute_description_count = vertex_input_attribute_descriptions.len, - .p_vertex_attribute_descriptions = &vertex_input_attribute_descriptions, + .vertex_input_state = .{ + .vertex_binding_descriptions = &.{ + .{ + .binding = 0, + .stride = @sizeOf(Vertex), + .input_rate = .vertex, + }, }, - .p_input_assembly_state = &.{ - .topology = .triangle_list, - .primitive_restart_enable = .false, + .vertex_attribute_descriptions = &.{ + .{ + .location = 0, + .binding = 0, + .format = .r32g32b32_sfloat, + .offset = @offsetOf(Vertex, "positionOS"), + }, + .{ + .location = 1, + .binding = 0, + .format = .r16g16_unorm, + .offset = @offsetOf(Vertex, "texCoord"), + }, + .{ + .location = 2, + .binding = 0, + .format = .r8g8b8_snorm, + .offset = @offsetOf(Vertex, "normalOS"), + }, + .{ + .location = 3, + .binding = 0, + .format = .r8g8b8a8_snorm, + .offset = @offsetOf(Vertex, "tangentOS"), + }, }, - .p_viewport_state = &.{ - .viewport_count = 1, - .p_viewports = undefined, - .scissor_count = 1, - .p_scissors = undefined, - }, - .p_rasterization_state = &.{ - .depth_clamp_enable = .false, - .rasterizer_discard_enable = .false, - .polygon_mode = .fill, - .cull_mode = .{ .back_bit = true }, - .front_face = .counter_clockwise, - .depth_bias_enable = .false, - .depth_bias_constant_factor = 0, - .depth_bias_clamp = 0, - .depth_bias_slope_factor = 0, - .line_width = 1, - }, - .p_multisample_state = &.{ - .rasterization_samples = .{ .@"1_bit" = true }, - .sample_shading_enable = .false, - .min_sample_shading = 1, - .alpha_to_coverage_enable = .false, - .alpha_to_one_enable = .false, - }, - .p_depth_stencil_state = null, - .p_color_blend_state = &.{ - .logic_op_enable = .false, - .logic_op = .copy, - .attachment_count = pipeline_color_blend_attachment_states.len, - .p_attachments = &pipeline_color_blend_attachment_states, - .blend_constants = .{ 0, 0, 0, 0 }, - }, - .p_dynamic_state = &.{ - .dynamic_state_count = dynamic_states.len, - .p_dynamic_states = &dynamic_states, - }, - .layout = pipeline_layout, - .render_pass = swapchain.render_pass, - .subpass = 0, - .base_pipeline_index = -1, }, - }, interface, @ptrCast(&pipeline)); - errdefer device.destroyPipeline(pipeline, interface); + .input_assembly_state = .{ + .topology = .triangle_list, + .primitive_restart_enable = .false, + }, + .viewport_state = .{ + .viewports = &.{undefined}, // dynamic + .scissors = &.{undefined}, // dynamic + }, + .rasterization_state = .{ + .depth_clamp_enable = .false, + .rasterizer_discard_enable = .false, + .polygon_mode = .fill, + .cull_mode = .{ .back_bit = true }, + .front_face = .counter_clockwise, + .depth_bias_enable = .false, + .depth_bias_constant_factor = 0, + .depth_bias_clamp = 0, + .depth_bias_slope_factor = 0, + .line_width = 1, + }, + .multisample_state = .{ + .rasterization_samples = .{ .@"1_bit" = true }, + .sample_shading_enable = .false, + .min_sample_shading = 1, + .alpha_to_coverage_enable = .false, + .alpha_to_one_enable = .false, + }, + .color_blend_state = .{ + .logic_op_enable = .false, + .logic_op = .copy, + .attachments = &.{ + .{ + .blend_enable = .false, + .src_color_blend_factor = .one, + .dst_color_blend_factor = .zero, + .color_blend_op = .add, + .src_alpha_blend_factor = .one, + .dst_alpha_blend_factor = .zero, + .alpha_blend_op = .add, + .color_write_mask = .{ + .r_bit = true, + .g_bit = true, + .b_bit = true, + .a_bit = true, + }, + }, + }, + .blend_constants = .{ 0, 0, 0, 0 }, + }, + .dynamic_state = .{ + .dynamic_states = &.{ .viewport, .scissor }, + }, + .layout = pipeline_layout, + .render_pass = swapchain.render_pass, + .subpass = 0, + }); + errdefer engine.destroyPipeline(pipeline); + + const descriptor_pool = try engine.createDescriptorPool(.{ + .max_sets = 2, + .pool_sizes = &.{ + .{ + .type = .sampler, + .descriptor_count = 1, + }, + .{ + .type = .sampled_image, + .descriptor_count = max_textures, + }, + .{ + .type = .uniform_buffer, + .descriptor_count = 1, + }, + .{ + .type = .storage_buffer, + .descriptor_count = 4, + }, + }, + }); + errdefer engine.destroyDescriptorPool(descriptor_pool); + + // [0] GLOBAL, [1] PER BATCH + var descriptor_sets: [2]vk.DescriptorSet = undefined; + try engine.allocateDescriptorSets(.{ + .descriptor_pool = descriptor_pool, + .set_layouts = &.{ global_descriptor_set_layout, per_batch_descriptor_set_layout }, + .variable_descriptor_counts = &.{ @intCast(textures.textures.items.len), 0 }, + }, &descriptor_sets); + + const descriptor_images = try allocator.alloc(vk.DescriptorImageInfo, textures.textures.items.len); + for (textures.textures.items, descriptor_images) |texture, *info| { + info.* = .{ + .sampler = .null_handle, + .image_view = texture.image_view, + .image_layout = .shader_read_only_optimal, + }; + } + defer allocator.free(descriptor_images); + + try engine.updateDescriptorSets(.{ + .writes = &.{ + .{ + .dst_set = descriptor_sets[0], + .dst_binding = 0, + .dst_array_element = 0, + .descriptor_type = .uniform_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = global_uniforms.buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + .{ + .dst_set = descriptor_sets[0], + .dst_binding = 1, + .dst_array_element = 0, + .descriptor_type = .storage_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = point_lights.buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + .{ + .dst_set = descriptor_sets[0], + .dst_binding = 2, + .dst_array_element = 0, + .descriptor_type = .storage_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = directional_lights.buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + .{ + .dst_set = descriptor_sets[0], + .dst_binding = 3, + .dst_array_element = 0, + .descriptor_type = .storage_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = materials.material_buffer.buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + .{ + .dst_set = descriptor_sets[0], + .dst_binding = 5, + .dst_array_element = 0, + .descriptor_type = .sampled_image, + .descriptor_infos = .{ .image = descriptor_images }, + }, + .{ + .dst_set = descriptor_sets[1], + .dst_binding = 0, + .dst_array_element = 0, + .descriptor_type = .storage_buffer, + .descriptor_infos = .{ + .buffer = &.{ + .{ + .buffer = object_uniforms.buffer, + .offset = 0, + .range = vk.WHOLE_SIZE, + }, + }, + }, + }, + }, + }); return .{ .allocator = allocator, + .engine = engine, .swapchain = swapchain, - .atoms = atoms, - .materials = materials, - .textures = textures, .global_descriptor_set_layout = global_descriptor_set_layout, - .per_object_descriptor_set_layout = per_object_descriptor_set_layout, + .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, + .descriptor_pool = descriptor_pool, + .descriptor_sets = descriptor_sets, .pipeline_layout = pipeline_layout, .pipeline = pipeline, + .vertex_buffer = vertex_buffer, .index_buffer = index_buffer, + + .global_uniforms = global_uniforms, + .point_lights = point_lights, + .directional_lights = directional_lights, + .object_uniforms = object_uniforms, + .sampler = sampler, + + .materials = materials, + .textures = textures, }; } pub fn deinit(self: *Game) void { - const device = self.swapchain.engine.device; - const interface = &self.swapchain.engine.vk_allocator.interface; + self.vertex_buffer.deinit(self.engine); + self.index_buffer.deinit(self.engine); - self.vertex_buffer.deinit(self.swapchain.engine); - self.index_buffer.deinit(self.swapchain.engine); + self.global_uniforms.deinit(self.engine); + self.point_lights.deinit(self.engine); + self.directional_lights.deinit(self.engine); + self.object_uniforms.deinit(self.engine); - device.destroyPipeline(self.pipeline, interface); - device.destroyPipelineLayout(self.pipeline_layout, interface); - device.destroyDescriptorSetLayout(self.per_object_descriptor_set_layout, interface); - device.destroyDescriptorSetLayout(self.global_descriptor_set_layout, interface); + self.engine.destroyDescriptorPool(self.descriptor_pool); + self.engine.destroySampler(self.sampler); + self.engine.destroyPipeline(self.pipeline); + self.engine.destroyPipelineLayout(self.pipeline_layout); + self.engine.destroyDescriptorSetLayout(self.per_batch_descriptor_set_layout); + self.engine.destroyDescriptorSetLayout(self.global_descriptor_set_layout); - self.textures.deinit(self.swapchain.engine); - self.materials.deinit(self.swapchain.engine); - self.atoms.deinit(); + self.textures.deinit(self.engine, self.allocator); + self.materials.deinit(self.engine, self.allocator); self.* = undefined; } @@ -378,8 +603,93 @@ pub fn update(self: *Game, dt: f32) void { self.camera_position = Vector3.add(self.camera_position, camera_d.asVector3(0)); + const framebuffer_size = Vector2.init( + @floatFromInt(self.swapchain.extent.width), + @floatFromInt(self.swapchain.extent.height), + ); + + const camera_rotation = Quaternion.mulQuaternion( + .initRotationXY(self.camera_yaw), + .initRotationYZ(self.camera_pitch), + ); + const camera_aspect_ratio = framebuffer_size.getX() / framebuffer_size.getY(); + const camera_yscale = 1.0 / @tan(camera_half_vertical_fov_rad); + const camera_xscale = camera_yscale / camera_aspect_ratio; + + const matrix_ws_to_vs = Matrix4x4.mulMatrix( + Matrix4x4.initRotation(camera_rotation.conjugate()), + Matrix4x4.initTranslation(self.camera_position.negate()), + ); + + // zig fmt: off + const matrix_vs_to_cs = Matrix4x4.init( + camera_xscale, 0, 0, 0, + 0, 0, camera_near_plane, 1, + 0, camera_yscale, 0, 0, + 0, 0, 0, 0, + ); + // zig fmt: on + + const ambient_light = Vector3.init(0.3, 0.3, 0.3); + + self.global_uniforms.write(self.engine, .{ + .header = .{ + .matrixWStoVS = matrix_ws_to_vs.asArray(), + .matrixVStoCS = matrix_vs_to_cs.asArray(), + .ambientLight = ambient_light.asArray(), + }, + }) catch |err| { + std.log.err("Failed to write global uniforms: {s}", .{@errorName(err)}); + @panic("Frame update failed"); + }; + + const point_lights: []const PointLight = &.{ + .{ + .positionWS = .{ 0, 0, 1 }, + .color = .{ 10, 10, 10 }, + }, + .{ + .positionWS = .{ -10, 10, 1 }, + .color = .{ 5, 0, 0 }, + }, + .{ + .positionWS = .{ 10, 10, 1 }, + .color = .{ 0, 0, 5 }, + }, + .{ + .positionWS = .{ -10, -10, 1 }, + .color = .{ 0, 5, 0 }, + }, + .{ + .positionWS = .{ 10, -10, 1 }, + .color = .{ 5, 5, 0 }, + }, + }; + self.point_lights.write(self.engine, .{ + .header = @intCast(point_lights.len), + .elements = point_lights, + }) catch |err| { + std.log.err("Failed to write point lights: {s}", .{@errorName(err)}); + @panic("Frame update failed"); + }; + + const directional_lights: []const DirectionalLight = &.{ + .{ + .directionWS = .{ 0, 0, -1 }, + .color = .{ 0.2, 0.2, 0.2 }, + }, + }; + self.directional_lights.write(self.engine, .{ + .header = @intCast(directional_lights.len), + .elements = directional_lights, + }) catch |err| { + std.log.err("Failed to write directional lights: {s}", .{@errorName(err)}); + @panic("Frame update failed"); + }; + self.render() catch |err| { std.log.err("Failed to render: {s}", .{@errorName(err)}); + @panic("Frame update failed"); }; } @@ -387,7 +697,7 @@ pub fn onKeyDown(self: *Game, key_code: glfw.Key, mods: glfw.Mods) void { const no_mods = @as(c_int, @bitCast(mods)) == 0; if (key_code == .escape and no_mods) { - self.swapchain.engine.mode.surface.window.setShouldClose(true); + self.engine.mode.surface.window.setShouldClose(true); } if (key_code == .w) { @@ -434,21 +744,40 @@ pub fn onMouseMove(self: *Game, dx: f32, dy: f32) void { } fn render(self: *Game) !void { - const engine = self.swapchain.engine; + const engine = self.engine; + const extent = self.swapchain.extent; - const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface); - defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); + const object_count: u32 = blk: { + var objects: std.ArrayList(ObjectUniforms) = try .initCapacity(self.engine.vk_allocator.allocator, 289); + defer objects.deinit(self.engine.vk_allocator.allocator); + + var it = Iterator3.init(.init(-8, -8, 0), .init(8, 8, 0), .one); + var material: u16 = 0; + while (it.next()) |pos| : (material = (material + 1) % @intFromEnum(self.materials.next_id)) { + const matrix_os_to_ws = Matrix4x4.initTranslation(pos); + const matrix_os_to_ws_normal = Matrix4x4.inverseTransposeAffine(matrix_os_to_ws); + objects.appendAssumeCapacity(.{ + .matrixOStoWS = matrix_os_to_ws.asArray(), + .matrixOStoWSNormal = matrix_os_to_ws_normal.asArray(), + .material = @enumFromInt(material), + }); + } + try self.object_uniforms.write(self.engine, .{ .elements = objects.items }); + + break :blk @intCast(objects.items.len); + }; const command_buffer = try engine.allocateGraphicsCommandBuffer(); - defer engine.freeGraphicsCommandBuffer(command_buffer); + // NOTE Do not free command buffer yet + try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); const viewports = [_]vk.Viewport{ .{ .x = 0, .y = 0, - .width = 0, - .height = 0, + .width = @floatFromInt(extent.width), + .height = @floatFromInt(extent.height), .min_depth = 0, .max_depth = 1, }, @@ -458,7 +787,7 @@ fn render(self: *Game) !void { const scissors = [_]vk.Rect2D{ .{ .offset = .{ .x = 0, .y = 0 }, - .extent = .{ .width = 0, .height = 0 }, + .extent = extent, }, }; command_buffer.setScissor(0, scissors.len, &scissors); @@ -473,7 +802,7 @@ fn render(self: *Game) !void { .framebuffer = self.swapchain.swapchain_images[0].framebuffer, .render_area = .{ .offset = .{ .x = 0, .y = 0 }, - .extent = .{ .width = 0, .height = 0 }, + .extent = extent, }, .clear_value_count = clear_values.len, .p_clear_values = &clear_values, @@ -484,10 +813,22 @@ fn render(self: *Game) !void { command_buffer.bindVertexBuffers(0, 1, @ptrCast(&self.vertex_buffer.buffer), &.{0}); command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); - command_buffer.drawIndexed(@intCast(self.index_buffer.index_count), 1, 0, 0, 0); + + command_buffer.bindDescriptorSets( + .graphics, + self.pipeline_layout, + 0, + self.descriptor_sets.len, + &self.descriptor_sets, + 0, + null, + ); + + command_buffer.drawIndexed(@intCast(self.index_buffer.array_capacity), object_count, 0, 0, 0); } try command_buffer.endCommandBuffer(); - try engine.submitGraphicsCommandBuffer(command_buffer, fence); - _ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); + const res = try self.swapchain.present(self.engine, command_buffer.handle); + + _ = res; } diff --git a/src/assets/Atoms.zig b/src/assets/Atoms.zig deleted file mode 100644 index d42507c..0000000 --- a/src/assets/Atoms.zig +++ /dev/null @@ -1,70 +0,0 @@ -pub const Atoms = @This(); -const std = @import("std"); - -allocator: std.mem.Allocator, -string_arena_state: std.heap.ArenaAllocator.State, -map: Map, -array: Array, - -pub const Atom = extern struct { - atom: u32, -}; - -pub const Map = std.StringHashMapUnmanaged(Atom); -pub const Array = std.ArrayList([]const u8); - -pub fn init(allocator: std.mem.Allocator) Atoms { - return .{ - .allocator = allocator, - .string_arena_state = .{}, - .map = .empty, - .array = .empty, - }; -} - -pub fn deinit(self: *Atoms) void { - self.string_arena_state.promote(std.heap.page_allocator).deinit(); - self.map.deinit(self.allocator); - self.array.deinit(self.allocator); - self.* = undefined; -} - -pub fn getString(self: *const Atoms, atom: Atom) ?[]const u8 { - const index: usize = atom.atom; - return if (index < self.array.items.len) self.array.items[index] else null; -} - -pub fn getAtom(self: *const Atoms, string: []const u8) ?Atom { - return self.map.get(string); -} - -pub fn getOrPutAtom(self: *Atoms, string: []const u8) !Atom { - const entry = try self.map.getOrPut(self.allocator, string); - - if (entry.found_existing) { - return entry.value_ptr.*; - } else { - errdefer _ = self.map.remove(string); - try self.array.ensureUnusedCapacity(self.allocator, 1); - const owned_string = try self.toOwnedString(string); - - const index = self.array.items.len; - const atom: Atom = .{ .atom = @intCast(index) }; - - entry.key_ptr.* = owned_string; - entry.value_ptr.* = atom; - - self.array.appendAssumeCapacity(owned_string); - - return atom; - } -} - -fn toOwnedString(self: *Atoms, string: []const u8) ![]const u8 { - var string_arena = self.string_arena_state.promote(std.heap.page_allocator); - defer self.string_arena_state = string_arena.state; - - const allocator = string_arena.allocator(); - const owned_string = try allocator.dupe(u8, string); - return owned_string; -} diff --git a/src/assets/Materials.zig b/src/assets/Materials.zig index 89508b4..b5564f6 100644 --- a/src/assets/Materials.zig +++ b/src/assets/Materials.zig @@ -3,76 +3,112 @@ const std = @import("std"); const vk = @import("vulkan"); -const Atoms = @import("Atoms.zig"); +const atoms = @import("../engine/atoms.zig"); const Engine = @import("../engine/Engine.zig"); -const StorageBuffer = @import("../engine/StorageBuffer.zig"); +const GenericBuffer = @import("../engine/GenericBuffer.zig").GenericBuffer; const Textures = @import("Textures.zig"); -allocator: std.mem.Allocator, +const MaterialBuffer = GenericBuffer(void, Material); + map: Map, -storage_buffer: StorageBuffer, +material_buffer: MaterialBuffer, next_id: Id, -pub const initial_capacity = 4; +// capacity * @sizeOf(Material) = 832 kiB +pub const capacity = std.math.maxInt(std.meta.Tag(Id)); -pub const Id = extern struct { - id: u32, +pub const Key = struct { atom: atoms.Atom }; +pub const Id = enum(u16) { + _, + + pub fn next(self: Id) Id { + return @enumFromInt(@intFromEnum(self) + 1); + } }; -pub const Map = std.AutoHashMapUnmanaged(Atoms.Atom, Id); +pub const Map = std.AutoHashMapUnmanaged(Key, Id); pub const Material = extern struct { base_color: [3]f32, - base_color_texture: Textures.Id, emissive: [3]f32, - emissive_texture: Textures.Id, ior: f32, metallic: f32, normal_scale: f32, - normal_texture: Textures.Id, - occlusion_roughness_metallic_texture: Textures.Id, occlusion_texture_strength: f32, roughness: f32, + + base_color_texture: Textures.Id, + emissive_texture: Textures.Id, + normal_texture: Textures.Id, + occlusion_roughness_metallic_texture: Textures.Id, }; -pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Materials { +pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Materials { var map: Map = .empty; errdefer map.deinit(allocator); + try map.ensureTotalCapacity(allocator, capacity); - var storage_buffer: StorageBuffer = try .init(engine, void, Material, initial_capacity); - errdefer storage_buffer.deinit(engine); + var material_buffer = try MaterialBuffer.init(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = capacity, + }); + errdefer material_buffer.deinit(engine); return .{ - .allocator = allocator, .map = map, - .storage_buffer = storage_buffer, - .next_id = .{ .id = 0 }, + .material_buffer = material_buffer, + .next_id = @enumFromInt(0), }; } -pub fn deinit(self: *Materials, engine: *Engine) void { - self.storage_buffer.deinit(engine); - self.map.deinit(self.allocator); +pub fn deinit(self: *Materials, engine: *Engine, allocator: std.mem.Allocator) void { + self.material_buffer.deinit(engine); + self.map.deinit(allocator); self.* = undefined; } -pub fn getId(self: *const Materials, key: Atoms.Atom) ?Id { +pub fn getAtom(self: *const Materials, atom: atoms.Atom) ?Id { + const key: Key = .{ .atom = atom }; return self.map.get(key); } -pub fn getOrLoadId(self: *Materials, engine: *Engine, textures: *Textures, atoms: *Atoms, key: Atoms.Atom) !Id { - const entry = try self.map.getOrPut(self.allocator, key); +pub fn getFilename(self: *const Materials, filename: []const u8) ?Id { + const atom = atoms.getAtom(filename) orelse return null; + const key: Key = .{ .atom = atom }; + return self.map.get(key); +} + +pub fn getOrLoadAtom(self: *Materials, engine: *Engine, textures: *Textures, atom: atoms.Atom, temp_allocator: std.mem.Allocator) !Id { + const key: Key = .{ .atom = atom }; + const entry = self.map.getOrPutAssumeCapacity(key); if (entry.found_existing) { return entry.value_ptr.*; } else { - const id = try self.loadMaterial(engine, textures, atoms, key); + errdefer _ = self.map.remove(key); + const id = try self.loadMaterial(engine, textures, atoms.getString(atom), temp_allocator); entry.value_ptr.* = id; return id; } } -fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *Atoms, key: Atoms.Atom) !Id { +pub fn getOrLoadFilename(self: *Materials, engine: *Engine, textures: *Textures, filename: []const u8, temp_allocator: std.mem.Allocator) !Id { + const atom = try atoms.getOrPutAtom(filename); + const key: Key = .{ .atom = atom }; + const entry = self.map.getOrPutAssumeCapacity(key); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + errdefer _ = self.map.remove(key); + const id = try self.loadMaterial(engine, textures, filename, temp_allocator); + entry.value_ptr.* = id; + return id; + } +} + +fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, filename: []const u8, temp_allocator: std.mem.Allocator) !Id { const MaterialJson = struct { baseColor: [3]f32 = .{ 1, 1, 1 }, baseColorTexture: ?[]const u8 = null, @@ -87,7 +123,6 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: * roughness: f32 = 1, }; - const filename = atoms.getString(key).?; std.log.debug("Loading material \"{s}\"...", .{filename}); const cwd = std.fs.cwd(); @@ -102,10 +137,10 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: * defer file.close(); var file_reader = file.reader(&buffer); - var json_reader: std.json.Reader = .init(self.allocator, &file_reader.interface); + var json_reader: std.json.Reader = .init(temp_allocator, &file_reader.interface); defer json_reader.deinit(); - const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, self.allocator, &json_reader, .{ + const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, temp_allocator, &json_reader, .{ .duplicate_field_behavior = .@"error", .ignore_unknown_fields = false, .allocate = .alloc_if_needed, @@ -116,65 +151,57 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: * const base_color_texture = blk: { if (material_json.baseColorTexture) |name| { - const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .base_color }); + break :blk try textures.getOrLoadFilename(engine, name, .base_color, temp_allocator); } else { - break :blk textures.empty_base_color; + break :blk .empty_base_color; } }; const emissive_texture = blk: { if (material_json.emissiveTexture) |name| { - const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .emissive }); + break :blk try textures.getOrLoadFilename(engine, name, .emissive, temp_allocator); } else { - break :blk textures.empty_emissive; + break :blk .empty_emissive; } }; const normal_texture = blk: { if (material_json.normalTexture) |name| { - const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .normal }); + break :blk try textures.getOrLoadFilename(engine, name, .normal, temp_allocator); } else { - break :blk textures.empty_normal; + break :blk .empty_normal; } }; const occlusion_roughness_metallic_texture = blk: { if (material_json.occlusionRoughnessMetallicTexture) |name| { - const atom = try atoms.getOrPutAtom(name); - break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .occlusion_roughness_metallic }); + break :blk try textures.getOrLoadFilename(engine, name, .occlusion_roughness_metallic, temp_allocator); } else { - break :blk textures.empty_occlusuion_roughness_metallic; + break :blk .empty_occlusion_roughness_metallic; } }; - const next_id = self.next_id; - const offset: usize = next_id.id; - - if (offset >= self.storage_buffer.array_capacity) { - try self.storage_buffer.enlarge(void, Material, engine, self.storage_buffer.array_capacity * 2); - } - - const materials = [_]Material{ - .{ - .base_color = material_json.baseColor, - .base_color_texture = base_color_texture, - .emissive = material_json.emissive, - .emissive_texture = emissive_texture, - .ior = material_json.ior, - .metallic = material_json.metallic, - .normal_scale = material_json.normalScale, - .normal_texture = normal_texture, - .occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture, - .occlusion_texture_strength = material_json.occlusionTextureStrength, - .roughness = material_json.roughness, + try self.material_buffer.write(engine, .{ + .element_offset = @intFromEnum(self.next_id), + .elements = &.{ + .{ + .base_color = material_json.baseColor, + .emissive = material_json.emissive, + .ior = material_json.ior, + .metallic = material_json.metallic, + .normal_scale = material_json.normalScale, + .occlusion_texture_strength = material_json.occlusionTextureStrength, + .roughness = material_json.roughness, + .base_color_texture = base_color_texture, + .emissive_texture = emissive_texture, + .normal_texture = normal_texture, + .occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture, + }, }, - }; + }); - try self.storage_buffer.writeOffset(void, Material, engine, {}, offset, &materials); - self.next_id = .{ .id = next_id.id + 1 }; + const id = self.next_id; + self.next_id = self.next_id.next(); - return next_id; + return id; } diff --git a/src/assets/Textures.zig b/src/assets/Textures.zig index a7185da..b0eed68 100644 --- a/src/assets/Textures.zig +++ b/src/assets/Textures.zig @@ -3,36 +3,41 @@ const std = @import("std"); const stbi = @import("zstbi"); -const Atoms = @import("Atoms.zig"); +const atoms = @import("../engine/atoms.zig"); const Engine = @import("../engine/Engine.zig"); const Texture = @import("../engine/Texture.zig"); -allocator: std.mem.Allocator, map: Map, -array: Array, +textures: Array, -empty_base_color: Id, -empty_emissive: Id, -empty_normal: Id, -empty_occlusuion_roughness_metallic: Id, - -pub const Id = extern struct { - id: u32, -}; +pub const capacity = std.math.maxInt(std.meta.Tag(Id)); pub const Key = struct { - atom: Atoms.Atom, + atom: atoms.Atom, usage: Texture.Usage, }; +pub const Id = enum(u16) { + empty_base_color = 0, + empty_emissive = 1, + empty_normal = 2, + empty_occlusion_roughness_metallic = 3, + _, + + pub fn next(self: Id) Id { + return @enumFromInt(@intFromEnum(self) + 1); + } +}; + pub const Map = std.AutoHashMapUnmanaged(Key, Id); pub const Array = std.ArrayList(Texture); -pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { +pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { var map: Map = .empty; errdefer map.deinit(allocator); + try map.ensureTotalCapacity(allocator, capacity); - var array: Array = try .initCapacity(allocator, 4); + var array: Array = try .initCapacity(allocator, capacity); errdefer { for (array.items) |*texture| { texture.deinit(engine); @@ -40,79 +45,116 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { array.deinit(allocator); } - const empty_base_color: Id = .{ .id = @intCast(array.items.len) }; - const empty_base_color_texture: Texture = try .init(engine, 1, 1, .base_color); - array.appendAssumeCapacity(empty_base_color_texture); + const empty_base_color_texture = try Texture.init(engine, .{ + .width = 1, + .height = 1, + .usage = .base_color, + .target_queue = .graphics, + }); - const empty_emissive: Id = .{ .id = @intCast(array.items.len) }; - const empty_emissive_texture: Texture = try .init(engine, 1, 1, .emissive); - array.appendAssumeCapacity(empty_emissive_texture); + const empty_emissive_texture = try Texture.init(engine, .{ + .width = 1, + .height = 1, + .usage = .emissive, + .target_queue = .graphics, + }); - const empty_normal: Id = .{ .id = @intCast(array.items.len) }; - const empty_normal_texture: Texture = try .init(engine, 1, 1, .normal); - array.appendAssumeCapacity(empty_normal_texture); + const empty_normal_texture = try Texture.init(engine, .{ + .width = 1, + .height = 1, + .usage = .normal, + .target_queue = .graphics, + }); - const empty_occlusuion_roughness_metallic: Id = .{ .id = @intCast(array.items.len) }; - const empty_occlusuion_roughness_metallic_texture: Texture = try .init(engine, 1, 1, .occlusion_roughness_metallic); - array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); + const empty_occlusuion_roughness_metallic_texture = try Texture.init(engine, .{ + .width = 1, + .height = 1, + .usage = .occlusion_roughness_metallic, + .target_queue = .graphics, + }); - try empty_base_color_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); - try empty_emissive_texture.write([4]f16, engine, &.{.{ 1.0, 1.0, 1.0, 1.0 }}); - try empty_normal_texture.write([4]i8, engine, &.{.{ 0, 0, 127, 127 }}); - try empty_occlusuion_roughness_metallic_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); + array.appendSliceAssumeCapacity(&.{ + empty_base_color_texture, + empty_emissive_texture, + empty_normal_texture, + empty_occlusuion_roughness_metallic_texture, + }); + + try empty_base_color_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 }); + try empty_emissive_texture.writeSamples(f16, engine, &.{ 1.0, 1.0, 1.0, 1.0 }); + try empty_normal_texture.writeSamples(i8, engine, &.{ 0, 0, 127, 127 }); + try empty_occlusuion_roughness_metallic_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 }); return .{ - .allocator = allocator, .map = map, - .array = array, - - .empty_base_color = empty_base_color, - .empty_emissive = empty_emissive, - .empty_normal = empty_normal, - .empty_occlusuion_roughness_metallic = empty_occlusuion_roughness_metallic, + .textures = array, }; } -pub fn deinit(self: *Textures, engine: *Engine) void { - for (self.array.items) |*texture| { +pub fn deinit(self: *Textures, engine: *Engine, allocator: std.mem.Allocator) void { + for (self.textures.items) |*texture| { texture.deinit(engine); } - self.array.deinit(self.allocator); + self.textures.deinit(allocator); + self.map.deinit(allocator); + self.* = undefined; +} - self.map.deinit(self.allocator); +pub fn getAtom(self: *const Textures, atom: atoms.Atom, usage: Texture.Usage) ?Id { + const key: Key = .{ .atom = atom, .usage = usage }; + return self.map.get(key); +} + +pub fn getFilename(self: *const Textures, filename: []const u8, usage: Texture.Usage) ?Id { + const atom = atoms.getAtom(filename) orelse return null; + const key: Key = .{ .atom = atom, .usage = usage }; + return self.map.get(key); } pub fn getTexture(self: *const Textures, id: Id) ?*Texture { const index: usize = id.id; - return if (index < self.array.items.len) &self.array.items[index] else null; + return if (index < self.textures.items.len) &self.textures.items[index] else null; } pub fn getId(self: *const Textures, key: Key) ?Id { return self.map.get(key); } -pub fn getOrLoadId(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Id { - const entry = try self.map.getOrPut(self.allocator, key); +pub fn getOrLoadAtom(self: *Textures, engine: *Engine, atom: atoms.Atom, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id { + const key: Key = .{ .atom = atom, .usage = usage }; + const entry = self.map.getOrPutAssumeCapacity(key); if (entry.found_existing) { return entry.value_ptr.*; } else { errdefer _ = self.map.remove(key); - try self.array.ensureUnusedCapacity(self.allocator, 1); - const texture = try self.loadTexture(engine, atoms, key); - + const texture = try self.loadTexture(engine, atoms.getString(atom), usage, temp_allocator); const id = self.nextId(); - entry.value_ptr.* = id; - self.array.appendAssumeCapacity(texture); - + self.textures.appendAssumeCapacity(texture); return id; } } -fn loadTexture(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Texture { - const filename = atoms.getString(key.atom).?; - std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(key.usage) }); +pub fn getOrLoadFilename(self: *Textures, engine: *Engine, filename: []const u8, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id { + const atom = try atoms.getOrPutAtom(filename); + const key: Key = .{ .atom = atom, .usage = usage }; + const entry = self.map.getOrPutAssumeCapacity(key); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + errdefer _ = self.map.remove(key); + const texture = try loadTexture(engine, filename, usage, temp_allocator); + const id = self.nextId(); + entry.value_ptr.* = id; + self.textures.appendAssumeCapacity(texture); + return id; + } +} + +fn loadTexture(engine: *Engine, filename: []const u8, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Texture { + std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(usage) }); const cwd = std.fs.cwd(); @@ -122,22 +164,26 @@ fn loadTexture(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Textu const file = try dir.openFile(filename, .{}); defer file.close(); - const file_buf = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize)); - defer self.allocator.free(file_buf); + const file_buf = try file.readToEndAlloc(temp_allocator, std.math.maxInt(usize)); + defer temp_allocator.free(file_buf); - var img = try stbi.Image.loadFromMemory(file_buf, key.usage.sampleCount()); + var img = try stbi.Image.loadFromMemory(file_buf, usage.samplesPerTexel()); defer img.deinit(); - std.debug.assert(img.num_components == key.usage.sampleCount()); + std.debug.assert(img.num_components == usage.samplesPerTexel()); - var texture: Texture = try .init(engine, img.width, img.height, key.usage); + var texture = try Texture.init(engine, .{ + .width = img.width, + .height = img.height, + .usage = usage, + .target_queue = .graphics, + }); errdefer texture.deinit(engine); - try texture.write(u8, engine, img.data); - + try texture.writeRaw(engine, img.data); return texture; } fn nextId(self: *const Textures) Id { - const index = self.array.items.len; - return .{ .id = @intCast(index) }; + const index = self.textures.items.len; + return @enumFromInt(index); } diff --git a/src/engine/Engine.zig b/src/engine/Engine.zig index 3cfa9af..e69467c 100644 --- a/src/engine/Engine.zig +++ b/src/engine/Engine.zig @@ -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)); +} diff --git a/src/engine/GenericBuffer.zig b/src/engine/GenericBuffer.zig new file mode 100644 index 0000000..3dd95f3 --- /dev/null +++ b/src/engine/GenericBuffer.zig @@ -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); + } + }; +} diff --git a/src/engine/IndexBuffer.zig b/src/engine/IndexBuffer.zig deleted file mode 100644 index 0be9607..0000000 --- a/src/engine/IndexBuffer.zig +++ /dev/null @@ -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)); -} diff --git a/src/engine/StagingBuffer.zig b/src/engine/StagingBuffer.zig index fa10bc8..87778ab 100644 --- a/src/engine/StagingBuffer.zig +++ b/src/engine/StagingBuffer.zig @@ -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); +} diff --git a/src/engine/StorageBuffer.zig b/src/engine/StorageBuffer.zig deleted file mode 100644 index 538cf88..0000000 --- a/src/engine/StorageBuffer.zig +++ /dev/null @@ -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)); -} diff --git a/src/engine/Swapchain.zig b/src/engine/Swapchain.zig index 7aafff8..5126518 100644 --- a/src/engine/Swapchain.zig +++ b/src/engine/Swapchain.zig @@ -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); } }; diff --git a/src/engine/TargetQueue.zig b/src/engine/TargetQueue.zig new file mode 100644 index 0000000..4768905 --- /dev/null +++ b/src/engine/TargetQueue.zig @@ -0,0 +1,4 @@ +pub const TargetQueue = enum { + graphics, + compute, +}; diff --git a/src/engine/Texture.zig b/src/engine/Texture.zig index 8ad0f73..11bec67 100644 --- a/src/engine/Texture.zig +++ b/src/engine/Texture.zig @@ -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); } diff --git a/src/engine/VertexBuffer.zig b/src/engine/VertexBuffer.zig deleted file mode 100644 index a1b1128..0000000 --- a/src/engine/VertexBuffer.zig +++ /dev/null @@ -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)); -} diff --git a/src/engine/atoms.zig b/src/engine/atoms.zig new file mode 100644 index 0000000..f6baaa8 --- /dev/null +++ b/src/engine/atoms.zig @@ -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; +} diff --git a/src/main.zig b/src/main.zig index d337ead..c64eaaa 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ const vk = @import("vulkan"); const c = @import("const.zig"); +const atoms = @import("engine/atoms.zig"); const Engine = @import("engine/Engine.zig"); const Swapchain = @import("engine/Swapchain.zig"); const Game = @import("Game.zig"); @@ -25,6 +26,9 @@ pub fn main() !void { allocator = gpa.allocator(); temp_allocator = fba.threadSafeAllocator(); + atoms.init(allocator); + defer atoms.deinit(); + stbi.init(allocator); defer stbi.deinit(); @@ -52,9 +56,9 @@ pub fn main() !void { defer engine.deinit(); var swapchain = try Swapchain.init(&engine); - defer swapchain.deinit(); + defer swapchain.deinit(&engine); - var game = try Game.init(allocator, &swapchain); + var game = try Game.init(allocator, &engine, &swapchain); defer game.deinit(); var t1 = glfw.getTime(); diff --git a/src/math/Vector2.zig b/src/math/Vector2.zig index 419f326..0f5b964 100644 --- a/src/math/Vector2.zig +++ b/src/math/Vector2.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + const Vector3 = @import("Vector3.zig").Vector3; const Vector4 = @import("Vector3.zig").Vector4; @@ -28,6 +30,11 @@ pub const Vector2 = extern struct { return self.vector; } + pub inline fn asArrayNorm(self: Vector2, comptime T: type) [2]T { + const scale_vector: Vector = @splat(std.math.maxInt(T)); + return @as(@Vector(2, T), @intFromFloat(@round(self.vector * scale_vector))); + } + pub inline fn asVector3(self: Vector2, z: f32) Vector3 { const z_vector: @Vector(3, f32) = .{ undefined, undefined, z }; return .{ .vector = @shuffle(f32, self.vector, z_vector, [_]i32{ 0, 1, ~@as(i32, 2) }) }; diff --git a/src/math/Vector3.zig b/src/math/Vector3.zig index f9229af..17ca1f7 100644 --- a/src/math/Vector3.zig +++ b/src/math/Vector3.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + const Quaternion = @import("Quaternion.zig").Quaternion; const Vector2 = @import("Vector2.zig").Vector2; const Vector4 = @import("Vector4.zig").Vector4; @@ -30,6 +32,11 @@ pub const Vector3 = extern struct { return self.vector; } + pub inline fn asArrayNorm(self: Vector3, comptime T: type) [3]T { + const scale_vector: Vector = @splat(std.math.maxInt(T)); + return @as(@Vector(3, T), @intFromFloat(@round(self.vector * scale_vector))); + } + pub inline fn asVector2(self: Vector3) Vector2 { return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) }; } diff --git a/src/math/Vector4.zig b/src/math/Vector4.zig index 3e64694..9e9e7d4 100644 --- a/src/math/Vector4.zig +++ b/src/math/Vector4.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + const Vector2 = @import("Vector2.zig").Vector2; const Vector3 = @import("Vector3.zig").Vector3; @@ -30,6 +32,11 @@ pub const Vector4 = extern struct { return self.vector; } + pub inline fn asArrayNorm(self: Vector4, comptime T: type) [4]T { + const scale_vector: Vector = @splat(std.math.maxInt(T)); + return @as(@Vector(4, T), @intFromFloat(@round(self.vector * scale_vector))); + } + pub inline fn asVector2(self: Vector4) Vector2 { return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) }; } diff --git a/src/voxel.zig b/src/voxel.zig new file mode 100644 index 0000000..e769261 --- /dev/null +++ b/src/voxel.zig @@ -0,0 +1,23 @@ +pub const Orientation = enum(u4) { + negative_x, + positive_x, + negative_y, + positive_y, + negative_z, + positive_z, +}; + +// ┌────────────────── x +// │ ┌────────────── y +// │ │ ┌───────── z +// │ │ │ ┌───── orientation +// │ │ │ │ ┌ material +// ┌┴─┐┌┴─┐ ┌┴─┐┌┴─┐ ┌┴──────────────┐ +// 10987654 32109876 54321098 76543210 +pub const Wall = packed struct(u32) { + material: u16, + orientation: Orientation, + z: u4, + y: u4, + x: u4, +};