Create descriptor set layout, pipeline, vertex buffer and index buffer

This commit is contained in:
2025-11-22 17:00:17 +01:00
parent c3efeaf452
commit d6a4b8c1fe
11 changed files with 395 additions and 62 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.zig-cache
zig-out
*.spv

View File

@@ -40,13 +40,13 @@ layout(set = 0, binding = 2, scalar) readonly buffer DirectionalLights {
DirectionalLight lights[];
} _DirectionalLights;
layout(set = 0, binding = 3) uniform sampler _Sampler;
layout(set = 0, binding = 4) uniform texture2D _Textures[];
layout(set = 0, binding = 5, scalar) readonly buffer Materials {
layout(set = 0, binding = 3, scalar) readonly buffer Materials {
Material _Materials[];
};
layout(set = 0, binding = 4) uniform sampler _Sampler;
layout(set = 0, binding = 5) uniform texture2D _Textures[];
// --- SET 1 --- PER OBJECT ----------------------------------------------------
layout(set = 1, binding = 0, scalar) uniform ObjectUniforms {

View File

@@ -20,6 +20,7 @@
},
.paths = .{
"assets",
"build.zig",
"build.zig.zon",
"src",

5
compile_shaders.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
glslc --target-env=vulkan1.2 assets/shaders/equirect_to_cube.comp -o src/shaders/equirect_to_cube_comp.spv
glslc --target-env=vulkan1.2 assets/shaders/main.frag -o src/shaders/main_frag.spv
glslc --target-env=vulkan1.2 assets/shaders/main.vert -o src/shaders/main_vert.spv

View File

@@ -2,6 +2,7 @@ const Game = @This();
const std = @import("std");
const glfw = @import("zglfw");
const vk = @import("vulkan");
const math = @import("math.zig");
@@ -9,14 +10,32 @@ const Atoms = @import("assets/Atoms.zig");
const Textures = @import("assets/Textures.zig");
const Materials = @import("assets/Materials.zig");
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 Vertex = extern struct {
positionOS: [3]f32,
texCoord: [2]u16,
normalOS: [3]i8,
tangentOS: [4]i8,
};
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,
swapchain: *Swapchain,
global_descriptor_set_layout: vk.DescriptorSetLayout,
per_object_descriptor_set_layout: vk.DescriptorSetLayout,
pipeline_layout: vk.PipelineLayout,
pipeline: vk.Pipeline,
vertex_buffer: VertexBuffer,
index_buffer: IndexBuffer,
atoms: Atoms,
materials: Materials,
@@ -52,6 +71,271 @@ pub fn init(allocator: std.mem.Allocator, swapchain: *Swapchain) !Game {
errdefer textures.deinit(swapchain.engine);
_ = 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 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 },
},
.{
.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 },
},
};
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,
},
.binding_count = global_descriptor_set_layout_bindings.len,
.p_bindings = &global_descriptor_set_layout_bindings,
}, interface);
errdefer device.destroyDescriptorSetLayout(global_descriptor_set_layout, interface);
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 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 descriptor_set_layouts = [_]vk.DescriptorSetLayout{
global_descriptor_set_layout,
per_object_descriptor_set_layout,
};
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 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 });
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",
},
};
const vertex_input_binding_descriptions = [_]vk.VertexInputBindingDescription{
.{
.binding = 0,
.stride = @sizeOf(Vertex),
.input_rate = .vertex,
},
};
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"),
},
};
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,
},
},
};
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,
},
.p_input_assembly_state = &.{
.topology = .triangle_list,
.primitive_restart_enable = .false,
},
.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);
return .{
.allocator = allocator,
@@ -59,10 +343,27 @@ pub fn init(allocator: std.mem.Allocator, swapchain: *Swapchain) !Game {
.atoms = atoms,
.materials = materials,
.textures = textures,
.global_descriptor_set_layout = global_descriptor_set_layout,
.per_object_descriptor_set_layout = per_object_descriptor_set_layout,
.pipeline_layout = pipeline_layout,
.pipeline = pipeline,
.vertex_buffer = vertex_buffer,
.index_buffer = index_buffer,
};
}
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.swapchain.engine);
self.index_buffer.deinit(self.swapchain.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.textures.deinit(self.swapchain.engine);
self.materials.deinit(self.swapchain.engine);
self.atoms.deinit();
@@ -76,6 +377,10 @@ pub fn update(self: *Game, dt: f32) void {
).rotate(self.camera_yaw).mulScalar(player_speed * dt);
self.camera_position = Vector3.add(self.camera_position, camera_d.asVector3(0));
self.render() catch |err| {
std.log.err("Failed to render: {s}", .{@errorName(err)});
};
}
pub fn onKeyDown(self: *Game, key_code: glfw.Key, mods: glfw.Mods) void {
@@ -127,3 +432,62 @@ pub fn onMouseMove(self: *Game, dx: f32, dy: f32) void {
self.camera_pitch = std.math.clamp(self.camera_pitch, -0.5 * std.math.pi, 0.5 * std.math.pi);
self.camera_yaw = @mod(self.camera_yaw, 2 * std.math.pi);
}
fn render(self: *Game) !void {
const engine = self.swapchain.engine;
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
const command_buffer = try engine.allocateGraphicsCommandBuffer();
defer engine.freeGraphicsCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
const viewports = [_]vk.Viewport{
.{
.x = 0,
.y = 0,
.width = 0,
.height = 0,
.min_depth = 0,
.max_depth = 1,
},
};
command_buffer.setViewport(0, viewports.len, &viewports);
const scissors = [_]vk.Rect2D{
.{
.offset = .{ .x = 0, .y = 0 },
.extent = .{ .width = 0, .height = 0 },
},
};
command_buffer.setScissor(0, scissors.len, &scissors);
{
const clear_values = [_]vk.ClearValue{
.{ .color = .{ .float_32 = .{ 0, 0, 0, 1 } } },
};
command_buffer.beginRenderPass(&.{
.render_pass = self.swapchain.render_pass,
.framebuffer = self.swapchain.swapchain_images[0].framebuffer,
.render_area = .{
.offset = .{ .x = 0, .y = 0 },
.extent = .{ .width = 0, .height = 0 },
},
.clear_value_count = clear_values.len,
.p_clear_values = &clear_values,
}, .@"inline");
defer command_buffer.endRenderPass();
command_buffer.bindPipeline(.graphics, self.pipeline);
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);
}
try command_buffer.endCommandBuffer();
try engine.submitGraphicsCommandBuffer(command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
}

View File

@@ -88,6 +88,7 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *
};
const filename = atoms.getString(key).?;
std.log.debug("Loading material \"{s}\"...", .{filename});
const cwd = std.fs.cwd();

View File

@@ -58,7 +58,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures {
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]u8, engine, &.{.{ 128, 128, 255, 255 }});
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 }});
return .{
@@ -112,6 +112,8 @@ pub fn getOrLoadId(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !I
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) });
const cwd = std.fs.cwd();
var dir = try cwd.openDir("assets/textures", .{});

View File

@@ -281,7 +281,15 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine {
try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name);
}
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
.descriptor_binding_partially_bound = .true,
.descriptor_binding_variable_descriptor_count = .true,
.runtime_descriptor_array = .true,
.scalar_block_layout = .true,
};
break :blk try instance.createDevice(physical_device, &.{
.p_next = &enabled_features_vulkan12,
.queue_create_info_count = @intCast(queue_create_info.items.len),
.p_queue_create_infos = queue_create_info.items.ptr,
.enabled_extension_count = @intCast(enabled_extensions.items.len),

View File

@@ -25,7 +25,7 @@ pub fn init(engine: *Engine, index_count: usize) !IndexBuffer {
.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);
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 });
@@ -42,7 +42,7 @@ pub fn init(engine: *Engine, index_count: usize) !IndexBuffer {
pub fn deinit(self: *IndexBuffer, engine: *Engine) void {
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
engine.device.destroyBuffer(self.buffer);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
self.* = undefined;
}
@@ -50,12 +50,12 @@ pub fn deinit(self: *IndexBuffer, engine: *Engine) void {
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);
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;
const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(indices), engine.graphics_queue.allocation.family);
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();

View File

@@ -27,7 +27,7 @@ pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !Ve
.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);
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 });
@@ -45,7 +45,7 @@ pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !Ve
pub fn deinit(self: *VertexBuffer, engine: *Engine) void {
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
engine.device.destroyBuffer(self.buffer);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
self.* = undefined;
}
@@ -54,12 +54,12 @@ pub fn write(self: VertexBuffer, comptime VertexType: type, engine: *Engine, ver
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);
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;
const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(vertices), engine.graphics_queue.allocation.family);
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();

View File

@@ -1,49 +0,0 @@
const std = @import("std");
const sokol = @import("sokol");
const sg = sokol.gfx;
const main = @import("main.zig");
pub var sampler_cache: std.AutoHashMapUnmanaged(Descriptor, sg.Sampler) = .{};
pub const Descriptor = struct {
wrap_u: sg.Wrap = .REPEAT,
wrap_v: sg.Wrap = .REPEAT,
wrap_w: sg.Wrap = .REPEAT,
min_filter: sg.Filter = .LINEAR,
mag_filter: sg.Filter = .LINEAR,
mipmap_filter: sg.Filter = .LINEAR,
};
pub fn getOrCreateSampler(descriptor: Descriptor) !sg.Sampler {
const entry = try sampler_cache.getOrPut(main.allocator, descriptor);
if (entry.found_existing) {
return entry.value_ptr.*;
} else {
const sampler = sg.makeSampler(.{
.wrap_u = descriptor.wrap_u,
.wrap_v = descriptor.wrap_v,
.wrap_w = descriptor.wrap_w,
.min_filter = descriptor.min_filter,
.mag_filter = descriptor.mag_filter,
.mipmap_filter = descriptor.mipmap_filter,
});
entry.key_ptr.* = descriptor;
entry.value_ptr.* = sampler;
return sampler;
}
}
pub fn deinit() void {
var sampler_cache_it = sampler_cache.valueIterator();
while (sampler_cache_it.next()) |sampler| {
sg.destroySampler(sampler.*);
}
sampler_cache.deinit(main.allocator);
}