Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
525fe1cc25 | |||
34135fa83e | |||
0134da7d95 | |||
31da379b40 | |||
0d4478acc6 | |||
d318dfa4a2 | |||
401f82c523 |
16 changed files with 1360 additions and 1152 deletions
33
build.zig
33
build.zig
|
@ -15,8 +15,6 @@ pub fn build(b: *std.Build) void {
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// exe.addIncludePath(b.path("include/"));
|
|
||||||
|
|
||||||
// --- Dependencies ---
|
// --- Dependencies ---
|
||||||
|
|
||||||
// Vulkan
|
// Vulkan
|
||||||
|
@ -26,22 +24,16 @@ pub fn build(b: *std.Build) void {
|
||||||
const vkzig_bindings = vkzig_dep.module("vulkan-zig");
|
const vkzig_bindings = vkzig_dep.module("vulkan-zig");
|
||||||
exe.root_module.addImport("vulkan", vkzig_bindings);
|
exe.root_module.addImport("vulkan", vkzig_bindings);
|
||||||
|
|
||||||
const shader_comp = vkgen.ShaderCompileStep.create(
|
// Shaders
|
||||||
b,
|
compileShader(b, exe, "shader_frag", "shader.frag");
|
||||||
.{ .real_path = "glslc" },
|
compileShader(b, exe, "shader_vert", "shader.vert");
|
||||||
&[_][]const u8{"--target-env=vulkan1.3"},
|
compileShader(b, exe, "second_frag", "second.frag");
|
||||||
"-o",
|
compileShader(b, exe, "second_vert", "second.vert");
|
||||||
);
|
|
||||||
shader_comp.add("shader_frag", "src/shaders/shader.frag", .{});
|
|
||||||
shader_comp.add("shader_vert", "src/shaders/shader.vert", .{});
|
|
||||||
shader_comp.add("second_frag", "src/shaders/second.frag", .{});
|
|
||||||
shader_comp.add("second_vert", "src/shaders/second.vert", .{});
|
|
||||||
exe.root_module.addImport("shaders", shader_comp.getModule());
|
|
||||||
|
|
||||||
// SDL2
|
// SDL2
|
||||||
const sdl_sdk = sdl.init(b, .{});
|
const sdl_sdk = sdl.init(b, .{});
|
||||||
sdl_sdk.link(exe, .dynamic, sdl.Library.SDL2);
|
sdl_sdk.link(exe, .dynamic, sdl.Library.SDL2);
|
||||||
exe.root_module.addImport("sdl2", sdl_sdk.getWrapperModuleVulkan(vkzig_bindings));
|
exe.root_module.addImport("sdl", sdl_sdk.getWrapperModuleVulkan(vkzig_bindings));
|
||||||
|
|
||||||
// zmath
|
// zmath
|
||||||
const zmath = b.dependency("zmath", .{});
|
const zmath = b.dependency("zmath", .{});
|
||||||
|
@ -91,3 +83,16 @@ pub fn build(b: *std.Build) void {
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compileShader(
|
||||||
|
b: *std.Build,
|
||||||
|
exe: *std.Build.Step.Compile,
|
||||||
|
comptime name: []const u8,
|
||||||
|
comptime file_name: []const u8,
|
||||||
|
) void {
|
||||||
|
const cmd = b.addSystemCommand(&.{ "glslc", "--target-env=vulkan1.3", "-o" });
|
||||||
|
|
||||||
|
const spv = cmd.addOutputFileArg(name ++ ".spv");
|
||||||
|
cmd.addFileArg(b.path("src/shaders/" ++ file_name));
|
||||||
|
exe.root_module.addAnonymousImport(name, .{ .root_source_file = spv });
|
||||||
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
.zstbi = .{ .path = "libs/zstbi" },
|
.zstbi = .{ .path = "libs/zstbi" },
|
||||||
.sdl = .{ .path = "libs/sdl" },
|
.sdl = .{ .path = "libs/sdl" },
|
||||||
.vulkan = .{
|
.vulkan = .{
|
||||||
.url = "https://github.com/Snektron/vulkan-zig/archive/f7b21d034f527765f62935de1b62855033621989.tar.gz",
|
.url = "https://github.com/Snektron/vulkan-zig/archive/06dae6c9201863837a92064e2e7814aa71064067.tar.gz",
|
||||||
.hash = "12201e484e173e70634e664864763223427703e677f28c63ebec9332513c8ca5121c",
|
.hash = "1220edeb3fc7dfc40e6fde705a108edce0a3cc76d165a7c9919d1fb037eccec43372",
|
||||||
},
|
|
||||||
.obj = .{
|
|
||||||
.url = "https://github.com/chip2n/zig-obj/archive/58f524ed6834790b29ac1e97b2f9e6b7de7b5346.tar.gz",
|
|
||||||
.hash = "1220ff46dcbeb40677c0ce8560b954885beec8b699835d9e6686beab72aa9d422c79",
|
|
||||||
},
|
},
|
||||||
|
// .obj = .{
|
||||||
|
// .url = "https://github.com/chip2n/zig-obj/archive/58f524ed6834790b29ac1e97b2f9e6b7de7b5346.tar.gz",
|
||||||
|
// .hash = "1220ff46dcbeb40677c0ce8560b954885beec8b699835d9e6686beab72aa9d422c79",
|
||||||
|
// },
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|
340
src/Context.zig
Normal file
340
src/Context.zig
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const sdl = @import("sdl");
|
||||||
|
const img = @import("zstbi");
|
||||||
|
|
||||||
|
const validation = @import("validation_layers.zig");
|
||||||
|
const Swapchain = @import("Swapchain.zig");
|
||||||
|
const QueueUtils = @import("queue_utils.zig");
|
||||||
|
|
||||||
|
const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
|
||||||
|
|
||||||
|
pub const apis: []const vk.ApiInfo = &.{
|
||||||
|
vk.features.version_1_0,
|
||||||
|
vk.features.version_1_1,
|
||||||
|
vk.features.version_1_2,
|
||||||
|
vk.features.version_1_3,
|
||||||
|
vk.extensions.khr_surface,
|
||||||
|
vk.extensions.khr_swapchain,
|
||||||
|
vk.extensions.ext_debug_utils,
|
||||||
|
};
|
||||||
|
|
||||||
|
const enable_validation_layers = builtin.mode == .Debug;
|
||||||
|
const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"};
|
||||||
|
|
||||||
|
const BaseDispatch = vk.BaseWrapper(apis);
|
||||||
|
const InstanceDispatch = vk.InstanceWrapper(apis);
|
||||||
|
const DeviceDispatch = vk.DeviceWrapper(apis);
|
||||||
|
|
||||||
|
pub const Instance = vk.InstanceProxy(apis);
|
||||||
|
pub const Device = vk.DeviceProxy(apis);
|
||||||
|
pub const Queue = vk.QueueProxy(apis);
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
vkb: BaseDispatch,
|
||||||
|
|
||||||
|
window: sdl.Window,
|
||||||
|
|
||||||
|
instance: Instance,
|
||||||
|
physical_device: vk.PhysicalDevice,
|
||||||
|
device: Device,
|
||||||
|
|
||||||
|
command_pool: vk.CommandPool,
|
||||||
|
graphics_queue: Queue,
|
||||||
|
presentation_queue: Queue,
|
||||||
|
surface: vk.SurfaceKHR,
|
||||||
|
swapchain: Swapchain,
|
||||||
|
|
||||||
|
debug_utils: ?vk.DebugUtilsMessengerEXT,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, window: sdl.Window) !Self {
|
||||||
|
var self: Self = undefined;
|
||||||
|
|
||||||
|
self.window = window;
|
||||||
|
self.allocator = allocator;
|
||||||
|
self.vkb = try BaseDispatch.load(try sdl.vulkan.getVkGetInstanceProcAddr());
|
||||||
|
|
||||||
|
img.init(allocator);
|
||||||
|
|
||||||
|
try self.createInstance();
|
||||||
|
|
||||||
|
if (enable_validation_layers) {
|
||||||
|
self.debug_utils = try validation.createDebugMessenger(self.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.createSurface();
|
||||||
|
|
||||||
|
try self.getPhysicalDevice();
|
||||||
|
try self.createLogicalDevice();
|
||||||
|
self.swapchain = try Swapchain.create(allocator, self);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
if (enable_validation_layers) {
|
||||||
|
self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.device.destroyDevice(null);
|
||||||
|
self.instance.destroySurfaceKHR(self.surface, null);
|
||||||
|
self.instance.destroyInstance(null);
|
||||||
|
|
||||||
|
self.allocator.destroy(self.device.wrapper);
|
||||||
|
self.allocator.destroy(self.instance.wrapper);
|
||||||
|
|
||||||
|
img.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createInstance(self: *Self) !void {
|
||||||
|
if (enable_validation_layers and !self.checkValidationLayersSupport()) {
|
||||||
|
// TODO Better error
|
||||||
|
return error.LayerNotPresent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensions = try self.getRequiredExtensions();
|
||||||
|
defer self.allocator.free(extensions);
|
||||||
|
|
||||||
|
std.debug.print("[Required instance extensions]\n", .{});
|
||||||
|
for (extensions) |ext| {
|
||||||
|
std.debug.print("\t- {s}\n", .{ext});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!try self.checkInstanceExtensions(&extensions)) {
|
||||||
|
return error.ExtensionNotPresent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app_info = vk.ApplicationInfo{
|
||||||
|
.p_application_name = "Vulkan SDL Test",
|
||||||
|
.application_version = vk.makeApiVersion(0, 0, 1, 0),
|
||||||
|
.p_engine_name = "Vulkan SDL Test",
|
||||||
|
.engine_version = vk.makeApiVersion(0, 0, 1, 0),
|
||||||
|
.api_version = vk.API_VERSION_1_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
var instance_create_info: vk.InstanceCreateInfo = .{
|
||||||
|
.p_application_info = &app_info,
|
||||||
|
.enabled_extension_count = @intCast(extensions.len),
|
||||||
|
.pp_enabled_extension_names = @ptrCast(extensions),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (enable_validation_layers) {
|
||||||
|
const debug_create_info = validation.getDebugUtilsCreateInfo();
|
||||||
|
|
||||||
|
instance_create_info.enabled_layer_count = @intCast(validation_layers.len);
|
||||||
|
instance_create_info.pp_enabled_layer_names = &validation_layers;
|
||||||
|
instance_create_info.p_next = &debug_create_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance_handle = try self.vkb.createInstance(&instance_create_info, null);
|
||||||
|
const vki = try self.allocator.create(InstanceDispatch);
|
||||||
|
errdefer self.allocator.destroy(vki);
|
||||||
|
vki.* = try InstanceDispatch.load(instance_handle, self.vkb.dispatch.vkGetInstanceProcAddr);
|
||||||
|
|
||||||
|
self.instance = Instance.init(instance_handle, vki);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createSurface(self: *Self) !void {
|
||||||
|
self.surface = try sdl.vulkan.createSurface(self.window, self.instance.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getPhysicalDevice(self: *Self) !void {
|
||||||
|
var pdev_count: u32 = 0;
|
||||||
|
_ = try self.instance.enumeratePhysicalDevices(&pdev_count, null);
|
||||||
|
|
||||||
|
const pdevs = try self.allocator.alloc(vk.PhysicalDevice, pdev_count);
|
||||||
|
defer self.allocator.free(pdevs);
|
||||||
|
|
||||||
|
_ = try self.instance.enumeratePhysicalDevices(&pdev_count, pdevs.ptr);
|
||||||
|
|
||||||
|
for (pdevs) |pdev| {
|
||||||
|
if (self.checkDeviceSuitable(pdev)) {
|
||||||
|
self.physical_device = pdev;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO Obviously needs to be something else
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createLogicalDevice(self: *Self) !void {
|
||||||
|
const indices = try QueueUtils.getQueueFamilies(self.*, self.physical_device);
|
||||||
|
// 1 is the highest priority
|
||||||
|
const priority = [_]f32{1};
|
||||||
|
|
||||||
|
const qci = [_]vk.DeviceQueueCreateInfo{
|
||||||
|
.{
|
||||||
|
.queue_family_index = indices.graphics_family.?,
|
||||||
|
.queue_count = 1,
|
||||||
|
.p_queue_priorities = &priority,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.queue_family_index = indices.presentation_family.?,
|
||||||
|
.queue_count = 1,
|
||||||
|
.p_queue_priorities = &priority,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const queue_count: u32 = if (indices.graphics_family.? == indices.presentation_family.?)
|
||||||
|
1
|
||||||
|
else
|
||||||
|
2;
|
||||||
|
|
||||||
|
// Device features
|
||||||
|
const device_features: vk.PhysicalDeviceFeatures = .{
|
||||||
|
.sampler_anisotropy = vk.TRUE, // Enable anisotropy
|
||||||
|
};
|
||||||
|
|
||||||
|
const device_create_info: vk.DeviceCreateInfo = .{
|
||||||
|
.queue_create_info_count = queue_count,
|
||||||
|
.p_queue_create_infos = &qci,
|
||||||
|
.pp_enabled_extension_names = &device_extensions,
|
||||||
|
.enabled_extension_count = @intCast(device_extensions.len),
|
||||||
|
.p_enabled_features = &device_features,
|
||||||
|
};
|
||||||
|
|
||||||
|
const device_handle = try self.instance.createDevice(self.physical_device, &device_create_info, null);
|
||||||
|
|
||||||
|
const vkd = try self.allocator.create(DeviceDispatch);
|
||||||
|
errdefer self.allocator.destroy(vkd);
|
||||||
|
vkd.* = try DeviceDispatch.load(device_handle, self.instance.wrapper.dispatch.vkGetDeviceProcAddr);
|
||||||
|
|
||||||
|
self.device = Device.init(device_handle, vkd);
|
||||||
|
|
||||||
|
const queues = try QueueUtils.getDeviceQueues(self.*);
|
||||||
|
|
||||||
|
self.graphics_queue = Queue.init(queues[0], self.device.wrapper);
|
||||||
|
self.presentation_queue = Queue.init(queues[1], self.device.wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createCommandPool(self: *Self) !void {
|
||||||
|
// Get indices of queue families from device
|
||||||
|
const queue_family_indices = try QueueUtils.getQueueFamilies(self.*, self.physical_device);
|
||||||
|
|
||||||
|
const pool_create_info: vk.CommandPoolCreateInfo = .{
|
||||||
|
// Queue family type that buffers from this command pool will use
|
||||||
|
.queue_family_index = queue_family_indices.graphics_family.?,
|
||||||
|
.flags = .{ .reset_command_buffer_bit = true },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a graphics queue family command pool
|
||||||
|
self.graphics_command_pool = try self.device.createCommandPool(&pool_create_info, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getRequiredExtensions(self: Self) ![][*:0]const u8 {
|
||||||
|
var ext_count = sdl.vulkan.getInstanceExtensionsCount(self.window);
|
||||||
|
|
||||||
|
if (enable_validation_layers) {
|
||||||
|
ext_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var extensions = try self.allocator.alloc([*:0]const u8, ext_count);
|
||||||
|
_ = try sdl.vulkan.getInstanceExtensions(self.window, extensions);
|
||||||
|
|
||||||
|
if (enable_validation_layers) {
|
||||||
|
extensions[extensions.len - 1] = vk.extensions.ext_debug_utils.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkInstanceExtensions(self: Self, required_extensions: *const [][*:0]const u8) !bool {
|
||||||
|
var prop_count: u32 = 0;
|
||||||
|
_ = try self.vkb.enumerateInstanceExtensionProperties(null, &prop_count, null);
|
||||||
|
|
||||||
|
const props = try self.allocator.alloc(vk.ExtensionProperties, prop_count);
|
||||||
|
defer self.allocator.free(props);
|
||||||
|
|
||||||
|
_ = try self.vkb.enumerateInstanceExtensionProperties(null, &prop_count, props.ptr);
|
||||||
|
|
||||||
|
for (required_extensions.*) |required_extension| {
|
||||||
|
for (props) |prop| {
|
||||||
|
if (std.mem.eql(u8, std.mem.sliceTo(&prop.extension_name, 0), std.mem.span(required_extension))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkDeviceExtensions(self: Self, pdev: vk.PhysicalDevice) !bool {
|
||||||
|
var prop_count: u32 = 0;
|
||||||
|
_ = try self.instance.enumerateDeviceExtensionProperties(pdev, null, &prop_count, null);
|
||||||
|
|
||||||
|
if (prop_count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = try self.allocator.alloc(vk.ExtensionProperties, prop_count);
|
||||||
|
defer self.allocator.free(props);
|
||||||
|
|
||||||
|
_ = try self.instance.enumerateDeviceExtensionProperties(pdev, null, &prop_count, props.ptr);
|
||||||
|
|
||||||
|
for (device_extensions) |device_extension| {
|
||||||
|
for (props) |prop| {
|
||||||
|
if (std.mem.eql(u8, std.mem.sliceTo(&prop.extension_name, 0), std.mem.span(device_extension))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkDeviceSuitable(self: Self, pdev: vk.PhysicalDevice) bool {
|
||||||
|
const pdev_properties = self.instance.getPhysicalDeviceProperties(pdev);
|
||||||
|
|
||||||
|
if (pdev_properties.device_type == .cpu) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdev_features = self.instance.getPhysicalDeviceFeatures(pdev);
|
||||||
|
const queue_family_indices = QueueUtils.getQueueFamilies(self, pdev) catch return false;
|
||||||
|
const extension_support = self.checkDeviceExtensions(pdev) catch return false;
|
||||||
|
|
||||||
|
const swapchain_details = Swapchain.getSwapchainDetails(
|
||||||
|
self.allocator,
|
||||||
|
self.instance,
|
||||||
|
pdev,
|
||||||
|
self.surface,
|
||||||
|
) catch return false;
|
||||||
|
defer self.allocator.free(swapchain_details.formats);
|
||||||
|
defer self.allocator.free(swapchain_details.presentation_modes);
|
||||||
|
|
||||||
|
const swapchain_valid = swapchain_details.formats.len != 0 and swapchain_details.formats.len != 0;
|
||||||
|
|
||||||
|
return queue_family_indices.isValid() and extension_support and swapchain_valid and pdev_features.sampler_anisotropy == vk.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkValidationLayersSupport(self: Self) bool {
|
||||||
|
var layer_count: u32 = undefined;
|
||||||
|
_ = self.vkb.enumerateInstanceLayerProperties(&layer_count, null) catch return false;
|
||||||
|
|
||||||
|
const available_layers = self.allocator.alloc(vk.LayerProperties, layer_count) catch unreachable;
|
||||||
|
defer self.allocator.free(available_layers);
|
||||||
|
|
||||||
|
_ = self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr) catch return false;
|
||||||
|
|
||||||
|
for (validation_layers) |validation_layer| {
|
||||||
|
for (available_layers) |available_layer| {
|
||||||
|
if (std.mem.eql(u8, std.mem.span(validation_layer), std.mem.sliceTo(&available_layer.layer_name, 0))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
12
src/Material.zig
Normal file
12
src/Material.zig
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
ctx: Context,
|
||||||
|
|
||||||
|
pub fn new(allocator: std.mem.Allocator, ctx: Context) Self {}
|
71
src/Mesh.zig
71
src/Mesh.zig
|
@ -2,16 +2,16 @@ const std = @import("std");
|
||||||
const vk = @import("vulkan");
|
const vk = @import("vulkan");
|
||||||
const zm = @import("zmath");
|
const zm = @import("zmath");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
const Utilities = @import("utilities.zig");
|
const Utilities = @import("utilities.zig");
|
||||||
const Vertex = Utilities.Vertex;
|
const Vertex = Utilities.Vertex;
|
||||||
const Device = @import("vulkan_renderer.zig").Device;
|
const Device = @import("Context.zig").Device;
|
||||||
const Instance = @import("vulkan_renderer.zig").Instance;
|
const Instance = @import("Context.zig").Instance;
|
||||||
const Model = @import("vulkan_renderer.zig").Model;
|
const Model = @import("vulkan_renderer.zig").Model;
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
ubo_model: Model,
|
ubo_model: Model,
|
||||||
tex_id: u32,
|
|
||||||
|
|
||||||
vertex_count: u32,
|
vertex_count: u32,
|
||||||
vertex_buffer: vk.Buffer,
|
vertex_buffer: vk.Buffer,
|
||||||
|
@ -21,32 +21,27 @@ index_count: u32,
|
||||||
index_buffer: vk.Buffer,
|
index_buffer: vk.Buffer,
|
||||||
index_buffer_memory: vk.DeviceMemory,
|
index_buffer_memory: vk.DeviceMemory,
|
||||||
|
|
||||||
instance: Instance,
|
ctx: Context,
|
||||||
physical_device: vk.PhysicalDevice,
|
|
||||||
device: Device,
|
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
pub fn new(
|
pub fn create(
|
||||||
instance: Instance,
|
allocator: std.mem.Allocator,
|
||||||
pdev: vk.PhysicalDevice,
|
ctx: Context,
|
||||||
device: Device,
|
|
||||||
transfer_queue: vk.Queue,
|
transfer_queue: vk.Queue,
|
||||||
transfer_command_pool: vk.CommandPool,
|
transfer_command_pool: vk.CommandPool,
|
||||||
vertices: []const Vertex,
|
vertices: []const Vertex,
|
||||||
indices: []const u32,
|
indices: []const u32,
|
||||||
tex_id: u32,
|
tex_id: u32,
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
) !Self {
|
) !Self {
|
||||||
var self: Self = undefined;
|
var self: Self = undefined;
|
||||||
|
|
||||||
|
self.allocator = allocator;
|
||||||
|
|
||||||
self.vertex_count = @intCast(vertices.len);
|
self.vertex_count = @intCast(vertices.len);
|
||||||
self.index_count = @intCast(indices.len);
|
self.index_count = @intCast(indices.len);
|
||||||
|
|
||||||
self.instance = instance;
|
self.ctx = ctx;
|
||||||
self.physical_device = pdev;
|
|
||||||
self.device = device;
|
|
||||||
self.allocator = allocator;
|
|
||||||
|
|
||||||
try self.createVertexBuffer(transfer_queue, transfer_command_pool, vertices);
|
try self.createVertexBuffer(transfer_queue, transfer_command_pool, vertices);
|
||||||
try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices);
|
try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices);
|
||||||
|
@ -57,12 +52,12 @@ pub fn new(
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroyBuffers(self: Self) void {
|
pub fn destroy(self: Self) void {
|
||||||
self.device.destroyBuffer(self.vertex_buffer, null);
|
self.ctx.device.destroyBuffer(self.vertex_buffer, null);
|
||||||
self.device.freeMemory(self.vertex_buffer_memory, null);
|
self.ctx.device.freeMemory(self.vertex_buffer_memory, null);
|
||||||
|
|
||||||
self.device.destroyBuffer(self.index_buffer, null);
|
self.ctx.device.destroyBuffer(self.index_buffer, null);
|
||||||
self.device.freeMemory(self.index_buffer_memory, null);
|
self.ctx.device.freeMemory(self.index_buffer_memory, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createVertexBuffer(
|
fn createVertexBuffer(
|
||||||
|
@ -77,14 +72,12 @@ fn createVertexBuffer(
|
||||||
// Temporary buffer to "stage" vertex data before transfering to GPU
|
// Temporary buffer to "stage" vertex data before transfering to GPU
|
||||||
var staging_buffer: vk.Buffer = undefined;
|
var staging_buffer: vk.Buffer = undefined;
|
||||||
var staging_buffer_memory: vk.DeviceMemory = undefined;
|
var staging_buffer_memory: vk.DeviceMemory = undefined;
|
||||||
defer self.device.destroyBuffer(staging_buffer, null);
|
defer self.ctx.device.destroyBuffer(staging_buffer, null);
|
||||||
defer self.device.freeMemory(staging_buffer_memory, null);
|
defer self.ctx.device.freeMemory(staging_buffer_memory, null);
|
||||||
|
|
||||||
// Create buffer and allocate memory to it
|
// Create buffer and allocate memory to it
|
||||||
try Utilities.createBuffer(
|
try Utilities.createBuffer(
|
||||||
self.physical_device,
|
self.ctx,
|
||||||
self.instance,
|
|
||||||
self.device,
|
|
||||||
buffer_size,
|
buffer_size,
|
||||||
.{ .transfer_src_bit = true },
|
.{ .transfer_src_bit = true },
|
||||||
.{ .host_visible_bit = true, .host_coherent_bit = true },
|
.{ .host_visible_bit = true, .host_coherent_bit = true },
|
||||||
|
@ -95,21 +88,19 @@ fn createVertexBuffer(
|
||||||
// Map memory to vertex
|
// Map memory to vertex
|
||||||
// 1. Create pointer to a point in normal memory
|
// 1. Create pointer to a point in normal memory
|
||||||
// 2. Map the vertex buffer memory to that point
|
// 2. Map the vertex buffer memory to that point
|
||||||
const data = try self.device.mapMemory(staging_buffer_memory, 0, buffer_size, .{});
|
const data = try self.ctx.device.mapMemory(staging_buffer_memory, 0, buffer_size, .{});
|
||||||
// 3. Copy memory from vertices vector to the point in memory
|
// 3. Copy memory from vertices vector to the point in memory
|
||||||
const gpu_vertices: [*]Vertex = @ptrCast(@alignCast(data));
|
const gpu_vertices: [*]Vertex = @ptrCast(@alignCast(data));
|
||||||
@memcpy(gpu_vertices, vertices[0..]);
|
@memcpy(gpu_vertices, vertices[0..]);
|
||||||
// 4. Unmap the vertex buffer memory
|
// 4. Unmap the vertex buffer memory
|
||||||
self.device.unmapMemory(staging_buffer_memory);
|
self.ctx.device.unmapMemory(staging_buffer_memory);
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
// Create buffer with TRANSFER_DST_BIT to mark as recipient of transfer data (also VERTEX_BUFFER)
|
// Create buffer with TRANSFER_DST_BIT to mark as recipient of transfer data (also VERTEX_BUFFER)
|
||||||
// Buffer memory is to be DEVICE_LOCAL_BIT meaning memory is on the GPU and only accessible by it and not CPU (host)
|
// Buffer memory is to be DEVICE_LOCAL_BIT meaning memory is on the GPU and only accessible by it and not CPU (host)
|
||||||
try Utilities.createBuffer(
|
try Utilities.createBuffer(
|
||||||
self.physical_device,
|
self.ctx,
|
||||||
self.instance,
|
|
||||||
self.device,
|
|
||||||
buffer_size,
|
buffer_size,
|
||||||
.{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
|
.{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
|
||||||
.{ .device_local_bit = true },
|
.{ .device_local_bit = true },
|
||||||
|
@ -119,7 +110,7 @@ fn createVertexBuffer(
|
||||||
|
|
||||||
// Copy staging buffer to vertex buffer on GPU
|
// Copy staging buffer to vertex buffer on GPU
|
||||||
try Utilities.copyBuffer(
|
try Utilities.copyBuffer(
|
||||||
self.device,
|
self.ctx,
|
||||||
transfer_queue,
|
transfer_queue,
|
||||||
transfer_command_pool,
|
transfer_command_pool,
|
||||||
staging_buffer,
|
staging_buffer,
|
||||||
|
@ -140,13 +131,11 @@ fn createIndexBuffer(
|
||||||
// Temporary buffer to "stage" vertex data before transfering to GPU
|
// Temporary buffer to "stage" vertex data before transfering to GPU
|
||||||
var staging_buffer: vk.Buffer = undefined;
|
var staging_buffer: vk.Buffer = undefined;
|
||||||
var staging_buffer_memory: vk.DeviceMemory = undefined;
|
var staging_buffer_memory: vk.DeviceMemory = undefined;
|
||||||
defer self.device.destroyBuffer(staging_buffer, null);
|
defer self.ctx.device.destroyBuffer(staging_buffer, null);
|
||||||
defer self.device.freeMemory(staging_buffer_memory, null);
|
defer self.ctx.device.freeMemory(staging_buffer_memory, null);
|
||||||
|
|
||||||
try Utilities.createBuffer(
|
try Utilities.createBuffer(
|
||||||
self.physical_device,
|
self.ctx,
|
||||||
self.instance,
|
|
||||||
self.device,
|
|
||||||
buffer_size,
|
buffer_size,
|
||||||
.{ .transfer_src_bit = true },
|
.{ .transfer_src_bit = true },
|
||||||
.{ .host_visible_bit = true, .host_coherent_bit = true },
|
.{ .host_visible_bit = true, .host_coherent_bit = true },
|
||||||
|
@ -155,16 +144,14 @@ fn createIndexBuffer(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Map memory to index buffer
|
// Map memory to index buffer
|
||||||
const data = try self.device.mapMemory(staging_buffer_memory, 0, buffer_size, .{});
|
const data = try self.ctx.device.mapMemory(staging_buffer_memory, 0, buffer_size, .{});
|
||||||
const gpu_vertices: [*]u32 = @ptrCast(@alignCast(data));
|
const gpu_vertices: [*]u32 = @ptrCast(@alignCast(data));
|
||||||
@memcpy(gpu_vertices, indices[0..]);
|
@memcpy(gpu_vertices, indices[0..]);
|
||||||
self.device.unmapMemory(staging_buffer_memory);
|
self.ctx.device.unmapMemory(staging_buffer_memory);
|
||||||
|
|
||||||
// Create buffer for index data on GPU access only
|
// Create buffer for index data on GPU access only
|
||||||
try Utilities.createBuffer(
|
try Utilities.createBuffer(
|
||||||
self.physical_device,
|
self.ctx,
|
||||||
self.instance,
|
|
||||||
self.device,
|
|
||||||
buffer_size,
|
buffer_size,
|
||||||
.{ .transfer_dst_bit = true, .index_buffer_bit = true },
|
.{ .transfer_dst_bit = true, .index_buffer_bit = true },
|
||||||
.{ .device_local_bit = true },
|
.{ .device_local_bit = true },
|
||||||
|
@ -174,7 +161,7 @@ fn createIndexBuffer(
|
||||||
|
|
||||||
// Copy from staging buffer to GPU access buffer
|
// Copy from staging buffer to GPU access buffer
|
||||||
try Utilities.copyBuffer(
|
try Utilities.copyBuffer(
|
||||||
self.device,
|
self.ctx,
|
||||||
transfer_queue,
|
transfer_queue,
|
||||||
transfer_command_pool,
|
transfer_command_pool,
|
||||||
staging_buffer,
|
staging_buffer,
|
||||||
|
|
|
@ -4,30 +4,93 @@ const zm = @import("zmath");
|
||||||
const ai = @import("assimp.zig").c;
|
const ai = @import("assimp.zig").c;
|
||||||
|
|
||||||
const Mesh = @import("Mesh.zig");
|
const Mesh = @import("Mesh.zig");
|
||||||
const Device = @import("vulkan_renderer.zig").Device;
|
const Context = @import("Context.zig");
|
||||||
const Instance = @import("vulkan_renderer.zig").Instance;
|
const Device = Context.Device;
|
||||||
|
const Instance = @import("Context.zig").Instance;
|
||||||
const Vertex = @import("utilities.zig").Vertex;
|
const Vertex = @import("utilities.zig").Vertex;
|
||||||
|
const StringUtils = @import("string_utils.zig");
|
||||||
|
const Texture = @import("Texture.zig");
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
mesh_list: std.ArrayList(Mesh),
|
mesh_list: std.ArrayList(Mesh),
|
||||||
|
textures: std.ArrayList(Texture),
|
||||||
model: zm.Mat,
|
model: zm.Mat,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, mesh_list: std.ArrayList(Mesh)) Self {
|
sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet),
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
ctx: Context,
|
||||||
|
graphics_command_pool: vk.CommandPool,
|
||||||
|
texture_sampler: vk.Sampler,
|
||||||
|
model_file: []const u8,
|
||||||
|
) Self {
|
||||||
|
_ = texture_sampler;
|
||||||
var new_mesh_model: Self = undefined;
|
var new_mesh_model: Self = undefined;
|
||||||
|
|
||||||
new_mesh_model.allocator = allocator;
|
new_mesh_model.allocator = allocator;
|
||||||
new_mesh_model.mesh_list = mesh_list;
|
|
||||||
new_mesh_model.model = zm.identity();
|
new_mesh_model.model = zm.identity();
|
||||||
|
|
||||||
|
new_mesh_model.sampler_descriptor_sets = try std.ArrayList(vk.DescriptorSet)
|
||||||
|
.init(allocator);
|
||||||
|
|
||||||
|
const path = try StringUtils.concat("assets/models/", model_file, allocator);
|
||||||
|
defer allocator.free(path);
|
||||||
|
|
||||||
|
// Import model scene
|
||||||
|
const scene = ai.aiImportFile(
|
||||||
|
path.ptr,
|
||||||
|
ai.aiProcess_Triangulate | ai.aiProcess_FlipUVs | ai.aiProcess_JoinIdenticalVertices,
|
||||||
|
);
|
||||||
|
defer ai.aiReleaseImport(scene);
|
||||||
|
|
||||||
|
// Get array of all materials with 1:1 ID placement
|
||||||
|
const texture_names = try ai.loadMaterials(allocator, scene);
|
||||||
|
defer {
|
||||||
|
for (0..texture_names.items.len) |i| {
|
||||||
|
if (texture_names.items[i]) |texture_name| {
|
||||||
|
allocator.free(texture_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
texture_names.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion from the material list IDs to our descriptor array IDs
|
||||||
|
new_mesh_model.textures = try std.ArrayList(Texture).initCapacity(allocator, texture_names.items.len);
|
||||||
|
|
||||||
|
// Loop over texture names and create textures for them
|
||||||
|
for (texture_names.items) |texture_name| {
|
||||||
|
if (texture_name != null) {
|
||||||
|
// Create texture and set value to index of new texture
|
||||||
|
new_mesh_model.textures.appendAssumeCapacity(try Texture.create(texture_name.?));
|
||||||
|
} else {
|
||||||
|
// If material had no texture, set to 0 to indicate no texture. Texture 0 will be reserver for a default texture
|
||||||
|
// TODO Put the default texture somewhere else where it's shared
|
||||||
|
new_mesh_model.textures.appendAssumeCapacity(try Texture.create("giraffe.png"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load in all our meshes
|
||||||
|
new_mesh_model.mesh_list = try loadNode(
|
||||||
|
allocator,
|
||||||
|
ctx.instance,
|
||||||
|
ctx.physical_device,
|
||||||
|
ctx.device,
|
||||||
|
ctx.graphics_queue.handle,
|
||||||
|
graphics_command_pool,
|
||||||
|
scene.*.mRootNode,
|
||||||
|
scene,
|
||||||
|
);
|
||||||
|
|
||||||
return new_mesh_model;
|
return new_mesh_model;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Self) void {
|
pub fn destroy(self: *Self) void {
|
||||||
for (0..self.mesh_list.items.len) |i| {
|
for (0..self.mesh_list.items.len) |i| {
|
||||||
self.mesh_list.items[i].destroyBuffers();
|
self.mesh_list.items[i].destroy();
|
||||||
}
|
}
|
||||||
self.mesh_list.deinit();
|
self.mesh_list.deinit();
|
||||||
}
|
}
|
||||||
|
@ -40,50 +103,6 @@ pub fn getMesh(self: Self, idx: usize) !Mesh {
|
||||||
return self.mesh_list.items[idx];
|
return self.mesh_list.items[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loadMaterials(allocator: std.mem.Allocator, scene: *const ai.aiScene) !std.ArrayList(?[]const u8) {
|
|
||||||
// Create 1:1 sized list of textures
|
|
||||||
var texture_list = try std.ArrayList(?[]const u8).initCapacity(allocator, scene.mNumMaterials);
|
|
||||||
|
|
||||||
// Go through each material and copy its texture file name (if it exists)
|
|
||||||
for (0..scene.mNumMaterials) |i| {
|
|
||||||
// Get the material
|
|
||||||
const material = scene.mMaterials[i];
|
|
||||||
|
|
||||||
// Initialise the texture to empty string (will be replaced if the texture exists)
|
|
||||||
// try texture_list.append("");
|
|
||||||
|
|
||||||
// Check for diffuse texture (standard detail texture)
|
|
||||||
if (ai.aiGetMaterialTextureCount(material, ai.aiTextureType_DIFFUSE) != 0) {
|
|
||||||
// Get the path of the texture file
|
|
||||||
var path: ai.aiString = undefined;
|
|
||||||
if (ai.aiGetMaterialTexture(
|
|
||||||
material,
|
|
||||||
ai.aiTextureType_DIFFUSE,
|
|
||||||
0,
|
|
||||||
&path,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
) == ai.AI_SUCCESS) {
|
|
||||||
// Cut of any directory information already present
|
|
||||||
var it = std.mem.splitBackwardsAny(u8, &path.data, "\\/");
|
|
||||||
if (it.next()) |filename| {
|
|
||||||
texture_list.appendAssumeCapacity(try allocator.dupe(u8, filename));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
texture_list.appendAssumeCapacity(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
texture_list.appendAssumeCapacity(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn loadNode(
|
pub fn loadNode(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
|
@ -93,7 +112,6 @@ pub fn loadNode(
|
||||||
transfer_command_pool: vk.CommandPool,
|
transfer_command_pool: vk.CommandPool,
|
||||||
node: *const ai.aiNode,
|
node: *const ai.aiNode,
|
||||||
scene: *const ai.aiScene,
|
scene: *const ai.aiScene,
|
||||||
mat_to_tex: []u32,
|
|
||||||
) !std.ArrayList(Mesh) {
|
) !std.ArrayList(Mesh) {
|
||||||
var mesh_list = std.ArrayList(Mesh).init(allocator);
|
var mesh_list = std.ArrayList(Mesh).init(allocator);
|
||||||
|
|
||||||
|
@ -108,7 +126,6 @@ pub fn loadNode(
|
||||||
transfer_queue,
|
transfer_queue,
|
||||||
transfer_command_pool,
|
transfer_command_pool,
|
||||||
scene.mMeshes[node.mMeshes[i]],
|
scene.mMeshes[node.mMeshes[i]],
|
||||||
mat_to_tex,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +140,6 @@ pub fn loadNode(
|
||||||
transfer_command_pool,
|
transfer_command_pool,
|
||||||
node.mChildren[i],
|
node.mChildren[i],
|
||||||
scene,
|
scene,
|
||||||
mat_to_tex,
|
|
||||||
);
|
);
|
||||||
defer new_list.deinit();
|
defer new_list.deinit();
|
||||||
|
|
||||||
|
@ -141,7 +157,6 @@ pub fn loadMesh(
|
||||||
transfer_queue: vk.Queue,
|
transfer_queue: vk.Queue,
|
||||||
transfer_command_pool: vk.CommandPool,
|
transfer_command_pool: vk.CommandPool,
|
||||||
mesh: *const ai.aiMesh,
|
mesh: *const ai.aiMesh,
|
||||||
mat_to_tex: []u32,
|
|
||||||
) !Mesh {
|
) !Mesh {
|
||||||
var vertices = try std.ArrayList(Vertex).initCapacity(allocator, mesh.mNumVertices);
|
var vertices = try std.ArrayList(Vertex).initCapacity(allocator, mesh.mNumVertices);
|
||||||
var indices = std.ArrayList(u32).init(allocator);
|
var indices = std.ArrayList(u32).init(allocator);
|
||||||
|
@ -180,7 +195,8 @@ pub fn loadMesh(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return try Mesh.new(
|
return try Mesh.create(
|
||||||
|
allocator,
|
||||||
instance,
|
instance,
|
||||||
pdev,
|
pdev,
|
||||||
device,
|
device,
|
||||||
|
@ -188,7 +204,6 @@ pub fn loadMesh(
|
||||||
transfer_command_pool,
|
transfer_command_pool,
|
||||||
vertices.items,
|
vertices.items,
|
||||||
indices.items,
|
indices.items,
|
||||||
mat_to_tex[mesh.mMaterialIndex],
|
mesh.mMaterialIndex,
|
||||||
allocator,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
55
src/ResourceManager.zig
Normal file
55
src/ResourceManager.zig
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Mesh = @import("Mesh.zig");
|
||||||
|
const Material = @import("Material.zig");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
ctx: Context,
|
||||||
|
|
||||||
|
sampler_descriptor_pool: vk.DescriptorPool,
|
||||||
|
sampler_descriptor_set_layout: vk.DescriptorSetLayout,
|
||||||
|
|
||||||
|
mesh_cache: std.AutoArrayHashMap([]const u8, Mesh),
|
||||||
|
material_cache: std.AutoArrayHashMap([]const u8, Material),
|
||||||
|
|
||||||
|
pub fn new(allocator: std.mem.Allocator, ctx: Context) Self {
|
||||||
|
var self: Self = undefined;
|
||||||
|
|
||||||
|
self.allocator = allocator;
|
||||||
|
self.ctx = ctx;
|
||||||
|
|
||||||
|
self.mesh_cache = std.AutoArrayHashMap([]const u8, Mesh).init(allocator);
|
||||||
|
self.material_cache = std.AutoArrayHashMap([]const u8, Material).init(allocator);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
// TODO Release resources properly
|
||||||
|
self.mesh_cache.deinit();
|
||||||
|
self.material_cache.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getMesh(self: *Self, file_name: []const u8) !Mesh {
|
||||||
|
if (self.mesh_cache.get(file_name)) |mesh| {
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Create mesh
|
||||||
|
// load mesh
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocateDescriptorSet(self: *Self) !void {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createDescriptorSetLayout(self: *Self) !void {
|
||||||
|
// TODO
|
||||||
|
}
|
209
src/Swapchain.zig
Normal file
209
src/Swapchain.zig
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const sdl = @import("sdl");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Instance = Context.Instance;
|
||||||
|
const QueueUtils = @import("queue_utils.zig");
|
||||||
|
const Utilities = @import("utilities.zig");
|
||||||
|
const Image = @import("image.zig");
|
||||||
|
|
||||||
|
pub const SwapchainDetails = struct {
|
||||||
|
surface_capabilities: vk.SurfaceCapabilitiesKHR,
|
||||||
|
formats: []vk.SurfaceFormatKHR,
|
||||||
|
presentation_modes: []vk.PresentModeKHR,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SwapchainImage = struct {
|
||||||
|
image: vk.Image,
|
||||||
|
image_view: vk.ImageView,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
ctx: Context,
|
||||||
|
|
||||||
|
handle: vk.SwapchainKHR,
|
||||||
|
|
||||||
|
swapchain_images: []SwapchainImage,
|
||||||
|
swapchain_framebuffers: []vk.Framebuffer,
|
||||||
|
|
||||||
|
swapchain_image_format: vk.Format,
|
||||||
|
extent: vk.Extent2D,
|
||||||
|
|
||||||
|
pub fn create(allocator: std.mem.Allocator, context: Context) !Self {
|
||||||
|
var self: Self = undefined;
|
||||||
|
|
||||||
|
self.allocator = allocator;
|
||||||
|
self.ctx = context;
|
||||||
|
|
||||||
|
const swapchain_details = try getSwapchainDetails(allocator, context.instance, context.physical_device, context.surface);
|
||||||
|
defer self.allocator.free(swapchain_details.formats);
|
||||||
|
defer self.allocator.free(swapchain_details.presentation_modes);
|
||||||
|
|
||||||
|
// 1. Choose best surface format
|
||||||
|
const surface_format = chooseBestSurfaceFormat(swapchain_details.formats);
|
||||||
|
// 2. Choose best presentation mode
|
||||||
|
const present_mode = chooseBestPresentationMode(swapchain_details.presentation_modes);
|
||||||
|
// 3. Choose swapchain image resolution
|
||||||
|
const extent = chooseSwapExtent(&self.ctx.window, swapchain_details.surface_capabilities);
|
||||||
|
|
||||||
|
// How many images are in the swapchain? Get 1 more than the minimum to allow triple buffering
|
||||||
|
var image_count: u32 = swapchain_details.surface_capabilities.min_image_count + 1;
|
||||||
|
const max_image_count = swapchain_details.surface_capabilities.max_image_count;
|
||||||
|
|
||||||
|
// Clamp down if higher
|
||||||
|
// If 0, it means it's limitless
|
||||||
|
if (max_image_count != 0 and image_count > max_image_count) {
|
||||||
|
image_count = max_image_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var swapchain_create_info: vk.SwapchainCreateInfoKHR = .{
|
||||||
|
.image_format = surface_format.format,
|
||||||
|
.image_color_space = surface_format.color_space,
|
||||||
|
.present_mode = present_mode,
|
||||||
|
.image_extent = extent,
|
||||||
|
.min_image_count = image_count,
|
||||||
|
.image_array_layers = 1, // Number of layers for each image
|
||||||
|
.image_usage = .{ .color_attachment_bit = true }, // What attachment will images be used as
|
||||||
|
.pre_transform = swapchain_details.surface_capabilities.current_transform, // Transform to perform on swapchain images
|
||||||
|
.composite_alpha = .{ .opaque_bit_khr = true }, // How to handle blending images with external graphics (e.g.: other windows)
|
||||||
|
.clipped = vk.TRUE, // Whether to clip parts of images not in view (e.g.: behind another window, off-screen, etc...)
|
||||||
|
.old_swapchain = .null_handle, // Links old one to quickly share responsibilities in case it's been destroyed and replaced
|
||||||
|
.surface = context.surface,
|
||||||
|
.image_sharing_mode = .exclusive,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get queue family indices
|
||||||
|
const family_indices = try QueueUtils.getQueueFamilies(self.ctx, self.ctx.physical_device);
|
||||||
|
|
||||||
|
// If graphic and presentation families are different, then swapchain must let images be shared between families
|
||||||
|
|
||||||
|
if (family_indices.graphics_family.? != family_indices.presentation_family.?) {
|
||||||
|
const qfi = [_]u32{
|
||||||
|
family_indices.graphics_family.?,
|
||||||
|
family_indices.presentation_family.?,
|
||||||
|
};
|
||||||
|
|
||||||
|
swapchain_create_info.image_sharing_mode = .concurrent;
|
||||||
|
swapchain_create_info.queue_family_index_count = @intCast(qfi.len); // Number of queues to share images between
|
||||||
|
swapchain_create_info.p_queue_family_indices = &qfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle = try self.ctx.device.createSwapchainKHR(&swapchain_create_info, null);
|
||||||
|
self.swapchain_image_format = surface_format.format;
|
||||||
|
self.extent = extent;
|
||||||
|
|
||||||
|
// Swapchain images
|
||||||
|
var swapchain_image_count: u32 = 0;
|
||||||
|
_ = try self.ctx.device.getSwapchainImagesKHR(self.handle, &swapchain_image_count, null);
|
||||||
|
|
||||||
|
const images = try self.allocator.alloc(vk.Image, swapchain_image_count);
|
||||||
|
defer self.allocator.free(images);
|
||||||
|
|
||||||
|
_ = try self.ctx.device.getSwapchainImagesKHR(self.handle, &swapchain_image_count, images.ptr);
|
||||||
|
|
||||||
|
self.swapchain_images = try self.allocator.alloc(SwapchainImage, swapchain_image_count);
|
||||||
|
|
||||||
|
for (images, 0..) |image, i| {
|
||||||
|
self.swapchain_images[i] = .{
|
||||||
|
.image = image,
|
||||||
|
.image_view = try Image.createImageView(self.ctx, image, self.swapchain_image_format, .{ .color_bit = true }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Self) void {
|
||||||
|
for (self.swapchain_framebuffers) |framebuffer| {
|
||||||
|
self.ctx.device.destroyFramebuffer(framebuffer, null);
|
||||||
|
}
|
||||||
|
self.allocator.free(self.swapchain_framebuffers);
|
||||||
|
|
||||||
|
for (self.swapchain_images) |swapchain_image| {
|
||||||
|
self.ctx.device.destroyImageView(swapchain_image.image_view, null);
|
||||||
|
}
|
||||||
|
self.allocator.free(self.swapchain_images);
|
||||||
|
|
||||||
|
self.ctx.device.destroySwapchainKHR(self.handle, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSwapchainDetails(allocator: std.mem.Allocator, instance: Instance, pdev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !SwapchainDetails {
|
||||||
|
// Capabilities
|
||||||
|
const surface_capabilities = try instance.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, surface);
|
||||||
|
|
||||||
|
// Formats
|
||||||
|
var format_count: u32 = 0;
|
||||||
|
_ = try instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, null);
|
||||||
|
|
||||||
|
const formats = try allocator.alloc(vk.SurfaceFormatKHR, format_count);
|
||||||
|
_ = try instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, formats.ptr);
|
||||||
|
|
||||||
|
// Presentation modes
|
||||||
|
var present_mode_count: u32 = 0;
|
||||||
|
_ = try instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, null);
|
||||||
|
|
||||||
|
const presentation_modes = try allocator.alloc(vk.PresentModeKHR, format_count);
|
||||||
|
_ = try instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, presentation_modes.ptr);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.surface_capabilities = surface_capabilities,
|
||||||
|
.formats = formats,
|
||||||
|
.presentation_modes = presentation_modes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format: VK_FORMAT_R8G8B8A8_UNORM (VK_FORMAT_B8G8R8A8_UNORM as backup)
|
||||||
|
// Color space: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
|
||||||
|
fn chooseBestSurfaceFormat(formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR {
|
||||||
|
// If only one format available and is undefined, then this means all formats are available
|
||||||
|
if (formats.len == 1 and formats[0].format == vk.Format.undefined) {
|
||||||
|
return .{
|
||||||
|
.format = vk.Format.r8g8b8a8_srgb,
|
||||||
|
.color_space = vk.ColorSpaceKHR.srgb_nonlinear_khr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (formats) |format| {
|
||||||
|
if ((format.format == vk.Format.r8g8b8a8_srgb or format.format == vk.Format.b8g8r8a8_srgb) and format.color_space == vk.ColorSpaceKHR.srgb_nonlinear_khr) {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formats[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chooseBestPresentationMode(presentation_modes: []vk.PresentModeKHR) vk.PresentModeKHR {
|
||||||
|
for (presentation_modes) |presentation_mode| {
|
||||||
|
if (presentation_mode == vk.PresentModeKHR.mailbox_khr) {
|
||||||
|
return presentation_mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use FIFO as Vulkan spec says it must be present
|
||||||
|
return vk.PresentModeKHR.fifo_khr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chooseSwapExtent(window: *sdl.Window, surface_capabilities: vk.SurfaceCapabilitiesKHR) vk.Extent2D {
|
||||||
|
// If the current extent is at max value, the extent can vary. Otherwise it's the size of the window
|
||||||
|
if (surface_capabilities.current_extent.width != std.math.maxInt(u32)) {
|
||||||
|
return surface_capabilities.current_extent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If value can very, need to set the extent manually
|
||||||
|
const framebuffer_size = sdl.vulkan.getDrawableSize(window);
|
||||||
|
|
||||||
|
var extent: vk.Extent2D = .{
|
||||||
|
.width = @intCast(framebuffer_size.width),
|
||||||
|
.height = @intCast(framebuffer_size.height),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Surface also defines max and min, so make sure it's within boundaries by clamping values
|
||||||
|
extent.width = @max(surface_capabilities.min_image_extent.width, @min(surface_capabilities.max_image_extent.width, extent.width));
|
||||||
|
extent.height = @max(surface_capabilities.min_image_extent.height, @min(surface_capabilities.max_image_extent.height, extent.height));
|
||||||
|
|
||||||
|
return extent;
|
||||||
|
}
|
200
src/Texture.zig
Normal file
200
src/Texture.zig
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const img = @import("zstbi");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Image = @import("image.zig");
|
||||||
|
const Utilities = @import("utilities.zig");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
ctx: Context,
|
||||||
|
|
||||||
|
idx: u32,
|
||||||
|
texture_image: vk.Image,
|
||||||
|
texture_image_memory: vk.DeviceMemory,
|
||||||
|
texture_image_view: vk.ImageView,
|
||||||
|
|
||||||
|
sampler_descriptor_set: vk.DescriptorSet,
|
||||||
|
|
||||||
|
image_file: img.Image,
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
file_name: []const u8,
|
||||||
|
ctx: Context,
|
||||||
|
graphics_command_pool: vk.CommandPool,
|
||||||
|
texture_sampler: vk.Sampler,
|
||||||
|
sampler_set_layout: vk.DescriptorSetLayout,
|
||||||
|
sampler_descriptor_pool: vk.DescriptorPool,
|
||||||
|
) Self {
|
||||||
|
var self: Self = undefined;
|
||||||
|
|
||||||
|
self.ctx = ctx;
|
||||||
|
|
||||||
|
// Create texture image and get its location in the array
|
||||||
|
const texture_image_loc = try self.createTextureImage(file_name, graphics_command_pool);
|
||||||
|
|
||||||
|
// Create image view
|
||||||
|
self.texture_image_view = try Image.createImageView(
|
||||||
|
ctx,
|
||||||
|
self.texture_images.items[texture_image_loc],
|
||||||
|
.r8g8b8a8_srgb,
|
||||||
|
.{ .color_bit = true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create texture descriptor
|
||||||
|
try self.createTextureDescriptor(
|
||||||
|
texture_sampler,
|
||||||
|
sampler_set_layout,
|
||||||
|
sampler_descriptor_pool,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return location of set with texture
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createTextureImage(
|
||||||
|
self: *Self,
|
||||||
|
file_name: []const u8,
|
||||||
|
graphics_command_pool: vk.CommandPool,
|
||||||
|
) !u32 {
|
||||||
|
// Load image file
|
||||||
|
var width: u32 = undefined;
|
||||||
|
var height: u32 = undefined;
|
||||||
|
var image_size: vk.DeviceSize = undefined;
|
||||||
|
const image = try self.loadTextureFile(file_name, &width, &height, &image_size);
|
||||||
|
|
||||||
|
// Create staging buffer to hold loaded data, ready to copy to device
|
||||||
|
var image_staging_buffer: vk.Buffer = undefined;
|
||||||
|
var image_staging_buffer_memory: vk.DeviceMemory = undefined;
|
||||||
|
defer self.ctx.device.destroyBuffer(image_staging_buffer, null);
|
||||||
|
defer self.ctx.device.freeMemory(image_staging_buffer_memory, null);
|
||||||
|
|
||||||
|
try Utilities.createBuffer(
|
||||||
|
self.ctx.physical_device,
|
||||||
|
self.ctx.instance,
|
||||||
|
self.ctx.device,
|
||||||
|
image_size,
|
||||||
|
.{ .transfer_src_bit = true },
|
||||||
|
.{ .host_visible_bit = true, .host_coherent_bit = true },
|
||||||
|
&image_staging_buffer,
|
||||||
|
&image_staging_buffer_memory,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Copy data to staging buffer
|
||||||
|
const data = try self.ctx.device.mapMemory(image_staging_buffer_memory, 0, image_size, .{});
|
||||||
|
const image_data: [*]u8 = @ptrCast(@alignCast(data));
|
||||||
|
|
||||||
|
@memcpy(image_data, image[0..]);
|
||||||
|
self.ctx.device.unmapMemory(image_staging_buffer_memory);
|
||||||
|
|
||||||
|
// Create image to hold final texture
|
||||||
|
var tex_image_memory: vk.DeviceMemory = undefined;
|
||||||
|
const tex_image = try Image.createImage(
|
||||||
|
self.ctx,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
.r8g8b8a8_srgb,
|
||||||
|
.optimal,
|
||||||
|
.{ .transfer_dst_bit = true, .sampled_bit = true },
|
||||||
|
.{ .device_local_bit = true },
|
||||||
|
&tex_image_memory,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transition image to be DST for copy operation
|
||||||
|
try Utilities.transitionImageLayout(
|
||||||
|
self.ctx.device,
|
||||||
|
self.ctx.graphics_queue.handle,
|
||||||
|
graphics_command_pool,
|
||||||
|
tex_image,
|
||||||
|
.undefined,
|
||||||
|
.transfer_dst_optimal,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Copy data to image
|
||||||
|
try Utilities.copyImageBuffer(
|
||||||
|
self.ctx.device,
|
||||||
|
self.ctx.graphics_queue.handle,
|
||||||
|
graphics_command_pool,
|
||||||
|
image_staging_buffer,
|
||||||
|
tex_image,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transition image to be shader readable for shader usage
|
||||||
|
try Utilities.transitionImageLayout(
|
||||||
|
self.ctx.device,
|
||||||
|
self.ctx.graphics_queue.handle,
|
||||||
|
graphics_command_pool,
|
||||||
|
tex_image,
|
||||||
|
.transfer_dst_optimal,
|
||||||
|
.shader_read_only_optimal,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.texture_image = tex_image;
|
||||||
|
self.texture_image_memory = tex_image_memory;
|
||||||
|
|
||||||
|
// Return index of new texture image
|
||||||
|
return @intCast(self.texture_images.items.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createTextureDescriptor(
|
||||||
|
self: *Self,
|
||||||
|
texture_sampler: vk.Sampler,
|
||||||
|
sampler_set_layout: vk.DescriptorSetLayout,
|
||||||
|
sampler_descriptor_pool: vk.DescriptorPool,
|
||||||
|
) !u32 {
|
||||||
|
// Descriptor set allocation info
|
||||||
|
const set_alloc_info: vk.DescriptorSetAllocateInfo = .{
|
||||||
|
.descriptor_pool = sampler_descriptor_pool,
|
||||||
|
.descriptor_set_count = 1,
|
||||||
|
.p_set_layouts = @ptrCast(&sampler_set_layout),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate descriptor sets
|
||||||
|
try self.ctx.device.allocateDescriptorSets(&set_alloc_info, @ptrCast(&self.sampler_descriptor_set));
|
||||||
|
|
||||||
|
const image_info: vk.DescriptorImageInfo = .{
|
||||||
|
.image_layout = .shader_read_only_optimal, // Image layout when in use
|
||||||
|
.image_view = self.texture_image_view, // Image to bind to set
|
||||||
|
.sampler = texture_sampler, // Sampler to use for set
|
||||||
|
};
|
||||||
|
|
||||||
|
// Descriptor write info
|
||||||
|
const descriptor_write: vk.WriteDescriptorSet = .{
|
||||||
|
.dst_set = self.sampler_descriptor_set,
|
||||||
|
.dst_binding = 0,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.descriptor_type = .combined_image_sampler,
|
||||||
|
.descriptor_count = 1,
|
||||||
|
.p_image_info = @ptrCast(&image_info),
|
||||||
|
.p_buffer_info = undefined,
|
||||||
|
.p_texel_buffer_view = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the new descriptor set
|
||||||
|
self.ctx.device.updateDescriptorSets(1, @ptrCast(&descriptor_write), 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadTextureFile(self: *Self, file_name: []const u8, width: *u32, height: *u32, image_size: *vk.DeviceSize) !void {
|
||||||
|
const path_concat = [2][]const u8{ "./assets/textures/", file_name };
|
||||||
|
const path = try std.mem.concatWithSentinel(self.allocator, u8, &path_concat, 0);
|
||||||
|
defer self.allocator.free(path);
|
||||||
|
|
||||||
|
const image = try img.Image.loadFromFile(path, 0);
|
||||||
|
|
||||||
|
width.* = image.width;
|
||||||
|
height.* = image.height;
|
||||||
|
|
||||||
|
// Calculate image size using given and known data
|
||||||
|
image_size.* = width.* * height.* * 4;
|
||||||
|
|
||||||
|
self.image_file = image;
|
||||||
|
}
|
|
@ -1,9 +1,53 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
@cInclude("assimp/cimport.h");
|
@cInclude("assimp/cimport.h");
|
||||||
@cInclude("assimp/scene.h");
|
@cInclude("assimp/scene.h");
|
||||||
@cInclude("assimp/postprocess.h");
|
@cInclude("assimp/postprocess.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
// pub fn importFile(path: [:0]const u8, flags: c_uint) *const c.aiScene {
|
/// Load the texture material names in a scene.
|
||||||
// return c.aiImportFile(path.ptr, flags);
|
/// Don't forget to free each element after use.
|
||||||
// }
|
pub fn loadMaterials(allocator: std.mem.Allocator, scene: *const c.aiScene) !std.ArrayList(?[]const u8) {
|
||||||
|
// Create 1:1 sized list of textures
|
||||||
|
var texture_list = try std.ArrayList(?[]const u8).initCapacity(allocator, scene.mNumMaterials);
|
||||||
|
|
||||||
|
// Go through each material and copy its texture file name (if it exists)
|
||||||
|
for (0..scene.mNumMaterials) |i| {
|
||||||
|
// Get the material
|
||||||
|
const material = scene.mMaterials[i];
|
||||||
|
|
||||||
|
// Initialise the texture to empty string (will be replaced if the texture exists)
|
||||||
|
// try texture_list.append("");
|
||||||
|
|
||||||
|
// Check for diffuse texture (standard detail texture)
|
||||||
|
if (c.aiGetMaterialTextureCount(material, c.aiTextureType_DIFFUSE) != 0) {
|
||||||
|
// Get the path of the texture file
|
||||||
|
var path: c.aiString = undefined;
|
||||||
|
if (c.aiGetMaterialTexture(
|
||||||
|
material,
|
||||||
|
c.aiTextureType_DIFFUSE,
|
||||||
|
0,
|
||||||
|
&path,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
) == c.AI_SUCCESS) {
|
||||||
|
// Cut of any directory information already present
|
||||||
|
var it = std.mem.splitBackwardsAny(u8, &path.data, "\\/");
|
||||||
|
if (it.next()) |filename| {
|
||||||
|
texture_list.appendAssumeCapacity(try allocator.dupe(u8, filename));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
texture_list.appendAssumeCapacity(null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
texture_list.appendAssumeCapacity(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture_list;
|
||||||
|
}
|
||||||
|
|
82
src/image.zig
Normal file
82
src/image.zig
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Utilities = @import("utilities.zig");
|
||||||
|
|
||||||
|
pub fn createImage(
|
||||||
|
ctx: Context,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
format: vk.Format,
|
||||||
|
tiling: vk.ImageTiling,
|
||||||
|
use_flags: vk.ImageUsageFlags,
|
||||||
|
prop_flags: vk.MemoryPropertyFlags,
|
||||||
|
image_memory: *vk.DeviceMemory,
|
||||||
|
) !vk.Image {
|
||||||
|
// -- Create Image --
|
||||||
|
const image_create_info: vk.ImageCreateInfo = .{
|
||||||
|
.image_type = .@"2d", // Type of image (1D, 2D or 3D)
|
||||||
|
.extent = .{
|
||||||
|
.width = width, // Width of image extent
|
||||||
|
.height = height, // Height of image extent
|
||||||
|
.depth = 1, // Depth of image (just 1, no 3D aspecct)
|
||||||
|
},
|
||||||
|
.mip_levels = 1, // Number of mipmap levels
|
||||||
|
.array_layers = 1, // Number of level in image array
|
||||||
|
.format = format, // Format type of image
|
||||||
|
.tiling = tiling, // How image data should be tiled (arranged for optimal reading)
|
||||||
|
.initial_layout = .undefined, // Layout of image data on creation
|
||||||
|
.usage = use_flags, // Bit flags defining what image will be used for
|
||||||
|
.samples = .{ .@"1_bit" = true }, // Number of samples for multi-sampling
|
||||||
|
.sharing_mode = .exclusive, // Whether image can be shared between queues
|
||||||
|
};
|
||||||
|
|
||||||
|
const image = try ctx.device.createImage(&image_create_info, null);
|
||||||
|
|
||||||
|
// -- Create memory for image --
|
||||||
|
// Get memory requirements for a type of image
|
||||||
|
const memory_requirements = ctx.device.getImageMemoryRequirements(image);
|
||||||
|
|
||||||
|
// Allocate memory using image requirements and user-defined properties
|
||||||
|
const memory_alloc_info: vk.MemoryAllocateInfo = .{
|
||||||
|
.allocation_size = memory_requirements.size,
|
||||||
|
.memory_type_index = Utilities.findMemoryTypeIndex(ctx.physical_device, ctx.instance, memory_requirements.memory_type_bits, prop_flags),
|
||||||
|
};
|
||||||
|
|
||||||
|
image_memory.* = try ctx.device.allocateMemory(&memory_alloc_info, null);
|
||||||
|
|
||||||
|
// Connect memory to image
|
||||||
|
try ctx.device.bindImageMemory(image, image_memory.*, 0);
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createImageView(
|
||||||
|
ctx: Context,
|
||||||
|
image: vk.Image,
|
||||||
|
format: vk.Format,
|
||||||
|
aspect_flags: vk.ImageAspectFlags,
|
||||||
|
) !vk.ImageView {
|
||||||
|
const image_view_create_info: vk.ImageViewCreateInfo = .{
|
||||||
|
.image = image,
|
||||||
|
.format = format,
|
||||||
|
.view_type = .@"2d",
|
||||||
|
.components = .{
|
||||||
|
// Used for remapping rgba values to other rgba values
|
||||||
|
.r = .identity,
|
||||||
|
.g = .identity,
|
||||||
|
.b = .identity,
|
||||||
|
.a = .identity,
|
||||||
|
},
|
||||||
|
.subresource_range = .{
|
||||||
|
.aspect_mask = aspect_flags, // Which aspect of image to view (e.g.: colour, depth, stencil, etc...)
|
||||||
|
.base_mip_level = 0, // Start mipmap level to view from
|
||||||
|
.level_count = 1, // Number of mipmap levels to view
|
||||||
|
.base_array_layer = 0, // Start array level to view from
|
||||||
|
.layer_count = 1, // Number of array levels to view
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return try ctx.device.createImageView(&image_view_create_info, null);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const vk = @import("vulkan");
|
const vk = @import("vulkan");
|
||||||
const sdl = @import("sdl2");
|
const sdl = @import("sdl");
|
||||||
const zm = @import("zmath");
|
const zm = @import("zmath");
|
||||||
|
|
||||||
const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer;
|
const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer;
|
||||||
|
@ -62,7 +62,7 @@ pub fn main() !void {
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var vulkan_renderer = try VulkanRenderer.init(window, allocator);
|
var vulkan_renderer = try VulkanRenderer.init(allocator, window);
|
||||||
defer vulkan_renderer.deinit();
|
defer vulkan_renderer.deinit();
|
||||||
|
|
||||||
var delta = Delta.new();
|
var delta = Delta.new();
|
||||||
|
|
53
src/queue_utils.zig
Normal file
53
src/queue_utils.zig
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Instance = Context.Instance;
|
||||||
|
const Device = Context.Device;
|
||||||
|
|
||||||
|
pub const QueueFamilyIndices = struct {
|
||||||
|
graphics_family: ?u32 = null,
|
||||||
|
presentation_family: ?u32 = null,
|
||||||
|
|
||||||
|
pub fn isValid(self: QueueFamilyIndices) bool {
|
||||||
|
return self.graphics_family != null and self.presentation_family != null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn getQueueFamilies(ctx: Context, pdev: vk.PhysicalDevice) !QueueFamilyIndices {
|
||||||
|
var indices: QueueFamilyIndices = .{ .graphics_family = null };
|
||||||
|
|
||||||
|
var queue_family_count: u32 = 0;
|
||||||
|
ctx.instance.getPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_count, null);
|
||||||
|
|
||||||
|
const queue_family_list = try ctx.allocator.alloc(vk.QueueFamilyProperties, queue_family_count);
|
||||||
|
defer ctx.allocator.free(queue_family_list);
|
||||||
|
|
||||||
|
ctx.instance.getPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_count, queue_family_list.ptr);
|
||||||
|
|
||||||
|
for (queue_family_list, 0..) |queue_family, i| {
|
||||||
|
if (queue_family.queue_count > 0 and queue_family.queue_flags.graphics_bit) {
|
||||||
|
indices.graphics_family = @intCast(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const presentation_support = try ctx.instance.getPhysicalDeviceSurfaceSupportKHR(pdev, @intCast(i), ctx.surface);
|
||||||
|
if (queue_family.queue_count > 0 and presentation_support == vk.TRUE) {
|
||||||
|
indices.presentation_family = @intCast(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indices.isValid()) {
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDeviceQueues(ctx: Context) ![2]vk.Queue {
|
||||||
|
const indices = try getQueueFamilies(ctx, ctx.physical_device);
|
||||||
|
|
||||||
|
const graphics_queue = ctx.device.getDeviceQueue(indices.graphics_family.?, 0);
|
||||||
|
const presentation_queue = ctx.device.getDeviceQueue(indices.presentation_family.?, 0);
|
||||||
|
return .{ graphics_queue, presentation_queue };
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const vk = @import("vulkan");
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
const Instance = @import("vulkan_renderer.zig").Instance;
|
const Context = @import("Context.zig");
|
||||||
const Device = @import("vulkan_renderer.zig").Device;
|
const Instance = @import("Context.zig").Instance;
|
||||||
|
const Device = @import("Context.zig").Device;
|
||||||
const CommandBuffer = @import("vulkan_renderer.zig").CommandBuffer;
|
const CommandBuffer = @import("vulkan_renderer.zig").CommandBuffer;
|
||||||
|
|
||||||
pub const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
|
|
||||||
|
|
||||||
pub const Vector3 = @Vector(3, f32);
|
pub const Vector3 = @Vector(3, f32);
|
||||||
pub const Vector2 = @Vector(2, f32);
|
pub const Vector2 = @Vector(2, f32);
|
||||||
|
|
||||||
|
@ -17,29 +16,9 @@ pub const Vertex = struct {
|
||||||
tex: Vector2, // Texture coords (u, v)
|
tex: Vector2, // Texture coords (u, v)
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const QueueFamilyIndices = struct {
|
pub fn findMemoryTypeIndex(ctx: Context, allowed_types: u32, properties: vk.MemoryPropertyFlags) u32 {
|
||||||
graphics_family: ?u32 = null,
|
|
||||||
presentation_family: ?u32 = null,
|
|
||||||
|
|
||||||
pub fn isValid(self: QueueFamilyIndices) bool {
|
|
||||||
return self.graphics_family != null and self.presentation_family != null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SwapchainDetails = struct {
|
|
||||||
surface_capabilities: vk.SurfaceCapabilitiesKHR,
|
|
||||||
formats: []vk.SurfaceFormatKHR,
|
|
||||||
presentation_modes: []vk.PresentModeKHR,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SwapchainImage = struct {
|
|
||||||
image: vk.Image,
|
|
||||||
image_view: vk.ImageView,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn findMemoryTypeIndex(pdev: vk.PhysicalDevice, instance: Instance, allowed_types: u32, properties: vk.MemoryPropertyFlags) u32 {
|
|
||||||
// Get properties of physical device memory
|
// Get properties of physical device memory
|
||||||
const memory_properties = instance.getPhysicalDeviceMemoryProperties(pdev);
|
const memory_properties = ctx.instance.getPhysicalDeviceMemoryProperties(ctx.physical_device);
|
||||||
const mem_type_count = memory_properties.memory_type_count;
|
const mem_type_count = memory_properties.memory_type_count;
|
||||||
|
|
||||||
for (memory_properties.memory_types[0..mem_type_count], 0..mem_type_count) |mem_type, i| {
|
for (memory_properties.memory_types[0..mem_type_count], 0..mem_type_count) |mem_type, i| {
|
||||||
|
@ -54,9 +33,7 @@ pub fn findMemoryTypeIndex(pdev: vk.PhysicalDevice, instance: Instance, allowed_
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createBuffer(
|
pub fn createBuffer(
|
||||||
pdev: vk.PhysicalDevice,
|
ctx: Context,
|
||||||
instance: Instance,
|
|
||||||
device: Device,
|
|
||||||
buffer_size: vk.DeviceSize,
|
buffer_size: vk.DeviceSize,
|
||||||
buffer_usage: vk.BufferUsageFlags,
|
buffer_usage: vk.BufferUsageFlags,
|
||||||
buffer_properties: vk.MemoryPropertyFlags,
|
buffer_properties: vk.MemoryPropertyFlags,
|
||||||
|
@ -72,17 +49,16 @@ pub fn createBuffer(
|
||||||
.sharing_mode = .exclusive, // Similar to swapchain images, can share vertex buffers
|
.sharing_mode = .exclusive, // Similar to swapchain images, can share vertex buffers
|
||||||
};
|
};
|
||||||
|
|
||||||
buffer.* = try device.createBuffer(&buffer_create_info, null);
|
buffer.* = try ctx.device.createBuffer(&buffer_create_info, null);
|
||||||
|
|
||||||
// Get buffer memory requirements
|
// Get buffer memory requirements
|
||||||
const mem_requirements = device.getBufferMemoryRequirements(buffer.*);
|
const mem_requirements = ctx.device.getBufferMemoryRequirements(buffer.*);
|
||||||
|
|
||||||
// Allocate memory to buffer
|
// Allocate memory to buffer
|
||||||
const allocate_info: vk.MemoryAllocateInfo = .{
|
const allocate_info: vk.MemoryAllocateInfo = .{
|
||||||
.allocation_size = mem_requirements.size,
|
.allocation_size = mem_requirements.size,
|
||||||
.memory_type_index = findMemoryTypeIndex(
|
.memory_type_index = findMemoryTypeIndex(
|
||||||
pdev,
|
ctx,
|
||||||
instance,
|
|
||||||
mem_requirements.memory_type_bits, // Index of memory type of physical device that has required bit flags
|
mem_requirements.memory_type_bits, // Index of memory type of physical device that has required bit flags
|
||||||
// Host visible: CPU can interact with memory
|
// Host visible: CPU can interact with memory
|
||||||
// Host coherent: Allows placement of data straight into buffer after mapping (otherwise would have to specify manually)
|
// Host coherent: Allows placement of data straight into buffer after mapping (otherwise would have to specify manually)
|
||||||
|
@ -91,10 +67,10 @@ pub fn createBuffer(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allocate memory to vkDeviceMemory
|
// Allocate memory to vkDeviceMemory
|
||||||
buffer_memory.* = try device.allocateMemory(&allocate_info, null);
|
buffer_memory.* = try ctx.device.allocateMemory(&allocate_info, null);
|
||||||
|
|
||||||
// Allocate memory to given vertex buffer
|
// Allocate memory to given vertex buffer
|
||||||
try device.bindBufferMemory(buffer.*, buffer_memory.*, 0);
|
try ctx.device.bindBufferMemory(buffer.*, buffer_memory.*, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn beginCommandBuffer(device: Device, command_pool: vk.CommandPool) !CommandBuffer {
|
fn beginCommandBuffer(device: Device, command_pool: vk.CommandPool) !CommandBuffer {
|
||||||
|
|
61
src/validation_layers.zig
Normal file
61
src/validation_layers.zig
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
const Instance = @import("Context.zig").Instance;
|
||||||
|
|
||||||
|
// Validation layers stuff
|
||||||
|
pub fn createDebugMessenger(instance: Instance) !vk.DebugUtilsMessengerEXT {
|
||||||
|
const debug_create_info = getDebugUtilsCreateInfo();
|
||||||
|
|
||||||
|
return try instance.createDebugUtilsMessengerEXT(&debug_create_info, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDebugUtilsCreateInfo() vk.DebugUtilsMessengerCreateInfoEXT {
|
||||||
|
return vk.DebugUtilsMessengerCreateInfoEXT{
|
||||||
|
.message_severity = .{ .verbose_bit_ext = true, .warning_bit_ext = true, .error_bit_ext = true },
|
||||||
|
.message_type = .{ .general_bit_ext = true, .validation_bit_ext = true, .performance_bit_ext = true },
|
||||||
|
.pfn_user_callback = debugCallback,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debugCallback(
|
||||||
|
message_severity: vk.DebugUtilsMessageSeverityFlagsEXT,
|
||||||
|
message_types: vk.DebugUtilsMessageTypeFlagsEXT,
|
||||||
|
p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT,
|
||||||
|
p_user_data: ?*anyopaque,
|
||||||
|
) callconv(vk.vulkan_call_conv) vk.Bool32 {
|
||||||
|
_ = p_user_data;
|
||||||
|
const severity = getMessageSeverityLabel(message_severity);
|
||||||
|
const message_type = getMessageTypeLabel(message_types);
|
||||||
|
|
||||||
|
std.debug.print("[{s}] ({s}): {s}\n=====\n", .{ severity, message_type, p_callback_data.?.p_message.? });
|
||||||
|
return vk.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn getMessageSeverityLabel(message_severity: vk.DebugUtilsMessageSeverityFlagsEXT) []const u8 {
|
||||||
|
if (message_severity.verbose_bit_ext) {
|
||||||
|
return "VERBOSE";
|
||||||
|
} else if (message_severity.info_bit_ext) {
|
||||||
|
return "INFO";
|
||||||
|
} else if (message_severity.warning_bit_ext) {
|
||||||
|
return "WARNING";
|
||||||
|
} else if (message_severity.error_bit_ext) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn getMessageTypeLabel(message_types: vk.DebugUtilsMessageTypeFlagsEXT) []const u8 {
|
||||||
|
if (message_types.general_bit_ext) {
|
||||||
|
return "general";
|
||||||
|
} else if (message_types.validation_bit_ext) {
|
||||||
|
return "validation";
|
||||||
|
} else if (message_types.performance_bit_ext) {
|
||||||
|
return "performance";
|
||||||
|
} else if (message_types.device_address_binding_bit_ext) {
|
||||||
|
return "device_address_binding";
|
||||||
|
} else {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue