Refactor literally everything

This commit is contained in:
2025-11-26 01:19:20 +01:00
parent d6a4b8c1fe
commit 9f2d1e4608
22 changed files with 2070 additions and 1034 deletions

View File

@@ -1,13 +1,15 @@
#version 460 #version 460
#extension GL_EXT_nonuniform_qualifier : require #extension GL_EXT_nonuniform_qualifier : require
#extension GL_EXT_scalar_block_layout : require #extension GL_EXT_scalar_block_layout : require
#extension GL_EXT_shader_16bit_storage : require
in Varyings { in Varyings {
layout(location = 0) vec3 positionVS; layout(location = 0) flat uint instance;
layout(location = 1) vec2 texCoord; layout(location = 1) vec3 positionVS;
layout(location = 2) vec3 normalVS; layout(location = 2) vec2 texCoord;
layout(location = 3) vec3 tangentVS; layout(location = 3) vec3 normalVS;
layout(location = 4) vec3 bitangentVS; layout(location = 4) vec3 tangentVS;
layout(location = 5) vec3 bitangentVS;
} var; } var;
#include "main_common.glsl" #include "main_common.glsl"
@@ -78,13 +80,14 @@ vec4 texture2DAA(texture2D tex, vec2 texCoord) {
return texture(sampler2D(tex, _Sampler), texCoord); return texture(sampler2D(tex, _Sampler), texCoord);
} }
#define MATERIAL _Materials[_Object.material] #define OBJECT _Object[var.instance]
#define MATERIAL _Materials[uint(OBJECT.material)]
void main() { void main() {
vec4 baseColorTexel = texture2DAA(_Textures[MATERIAL.baseColorTexture], var.texCoord); vec4 baseColorTexel = texture2DAA(_Textures[uint(MATERIAL.baseColorTexture)], var.texCoord);
vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[MATERIAL.occlusionRoughnessMetallicTexture], var.texCoord); vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[uint(MATERIAL.occlusionRoughnessMetallicTexture)], var.texCoord);
vec4 normalTexel = texture2DAA(_Textures[MATERIAL.normalTexture], var.texCoord); vec4 normalTexel = texture2DAA(_Textures[uint(MATERIAL.normalTexture)], var.texCoord);
vec4 emissiveTexel = texture2DAA(_Textures[MATERIAL.emissiveTexture], var.texCoord); vec4 emissiveTexel = texture2DAA(_Textures[uint(MATERIAL.emissiveTexture)], var.texCoord);
vec3 baseColor = MATERIAL.baseColor * baseColorTexel.rgb; vec3 baseColor = MATERIAL.baseColor * baseColorTexel.rgb;
float occlusion = 1.0 + MATERIAL.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0); float occlusion = 1.0 + MATERIAL.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0);

View File

@@ -1,6 +1,7 @@
#version 460 #version 460
#extension GL_EXT_nonuniform_qualifier : require #extension GL_EXT_nonuniform_qualifier : require
#extension GL_EXT_scalar_block_layout : require #extension GL_EXT_scalar_block_layout : require
#extension GL_EXT_shader_16bit_storage : require
layout(location = 0) in vec3 positionOS; layout(location = 0) in vec3 positionOS;
layout(location = 1) in vec2 texCoord; layout(location = 1) in vec2 texCoord;
@@ -8,29 +9,33 @@ layout(location = 2) in vec3 normalOS;
layout(location = 3) in vec4 tangentOS; layout(location = 3) in vec4 tangentOS;
out Varyings { out Varyings {
layout(location = 0) vec3 positionVS; layout(location = 0) flat uint instance;
layout(location = 1) vec2 texCoord; layout(location = 1) vec3 positionVS;
layout(location = 2) vec3 normalVS; layout(location = 2) vec2 texCoord;
layout(location = 3) vec3 tangentVS; layout(location = 3) vec3 normalVS;
layout(location = 4) vec3 bitangentVS; layout(location = 4) vec3 tangentVS;
layout(location = 5) vec3 bitangentVS;
} var; } var;
#include "main_common.glsl" #include "main_common.glsl"
#define OBJECT _Object[gl_InstanceIndex]
void main() { void main() {
vec3 positionWS = (_Object.matrixOStoWS * vec4(positionOS, 1.0)).xyz; vec3 positionWS = (OBJECT.matrixOStoWS * vec4(positionOS, 1.0)).xyz;
vec3 positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz; vec3 positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz;
vec4 positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0); vec4 positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0);
vec3 normalWS = normalize((_Object.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz); vec3 normalWS = normalize((OBJECT.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz);
vec3 normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz); vec3 normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz);
vec3 tangentWS = normalize((_Object.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz); vec3 tangentWS = normalize((OBJECT.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz);
vec3 tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz); vec3 tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz);
vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS)); vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS));
gl_Position = positionCS; gl_Position = positionCS;
var.instance = gl_InstanceIndex;
var.positionVS = positionVS; var.positionVS = positionVS;
var.texCoord = texCoord; var.texCoord = texCoord;
var.normalVS = normalVS; var.normalVS = normalVS;

View File

@@ -12,16 +12,24 @@ struct DirectionalLight {
struct Material { struct Material {
vec3 baseColor; vec3 baseColor;
uint baseColorTexture;
vec3 emissive; vec3 emissive;
uint emissiveTexture;
float ior; float ior;
float metallic; float metallic;
float normalScale; float normalScale;
uint normalTexture;
uint occlusionRoughnessMetallicTexture;
float occlusionTextureStrength; float occlusionTextureStrength;
float roughness; float roughness;
uint16_t baseColorTexture;
uint16_t emissiveTexture;
uint16_t normalTexture;
uint16_t occlusionRoughnessMetallicTexture;
};
struct ObjectUniforms {
mat4 matrixOStoWS;
mat4 matrixOStoWSNormal;
uint16_t material;
}; };
layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { layout(set = 0, binding = 0, scalar) uniform GlobalUniforms {
@@ -47,11 +55,8 @@ layout(set = 0, binding = 3, scalar) readonly buffer Materials {
layout(set = 0, binding = 4) uniform sampler _Sampler; layout(set = 0, binding = 4) uniform sampler _Sampler;
layout(set = 0, binding = 5) uniform texture2D _Textures[]; layout(set = 0, binding = 5) uniform texture2D _Textures[];
// --- SET 1 --- PER OBJECT ---------------------------------------------------- // --- SET 1 --- PER BATCH -----------------------------------------------------
layout(set = 1, binding = 0, scalar) uniform ObjectUniforms { layout(set = 1, binding = 0, scalar) readonly buffer ObjectsUniforms {
mat4 matrixOStoWS; ObjectUniforms _Object[];
mat4 matrixOStoWSNormal; };
uint material;
} _Object;

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +0,0 @@
pub const Atoms = @This();
const std = @import("std");
allocator: std.mem.Allocator,
string_arena_state: std.heap.ArenaAllocator.State,
map: Map,
array: Array,
pub const Atom = extern struct {
atom: u32,
};
pub const Map = std.StringHashMapUnmanaged(Atom);
pub const Array = std.ArrayList([]const u8);
pub fn init(allocator: std.mem.Allocator) Atoms {
return .{
.allocator = allocator,
.string_arena_state = .{},
.map = .empty,
.array = .empty,
};
}
pub fn deinit(self: *Atoms) void {
self.string_arena_state.promote(std.heap.page_allocator).deinit();
self.map.deinit(self.allocator);
self.array.deinit(self.allocator);
self.* = undefined;
}
pub fn getString(self: *const Atoms, atom: Atom) ?[]const u8 {
const index: usize = atom.atom;
return if (index < self.array.items.len) self.array.items[index] else null;
}
pub fn getAtom(self: *const Atoms, string: []const u8) ?Atom {
return self.map.get(string);
}
pub fn getOrPutAtom(self: *Atoms, string: []const u8) !Atom {
const entry = try self.map.getOrPut(self.allocator, string);
if (entry.found_existing) {
return entry.value_ptr.*;
} else {
errdefer _ = self.map.remove(string);
try self.array.ensureUnusedCapacity(self.allocator, 1);
const owned_string = try self.toOwnedString(string);
const index = self.array.items.len;
const atom: Atom = .{ .atom = @intCast(index) };
entry.key_ptr.* = owned_string;
entry.value_ptr.* = atom;
self.array.appendAssumeCapacity(owned_string);
return atom;
}
}
fn toOwnedString(self: *Atoms, string: []const u8) ![]const u8 {
var string_arena = self.string_arena_state.promote(std.heap.page_allocator);
defer self.string_arena_state = string_arena.state;
const allocator = string_arena.allocator();
const owned_string = try allocator.dupe(u8, string);
return owned_string;
}

View File

@@ -3,76 +3,112 @@ const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const Atoms = @import("Atoms.zig"); const atoms = @import("../engine/atoms.zig");
const Engine = @import("../engine/Engine.zig"); const Engine = @import("../engine/Engine.zig");
const StorageBuffer = @import("../engine/StorageBuffer.zig"); const GenericBuffer = @import("../engine/GenericBuffer.zig").GenericBuffer;
const Textures = @import("Textures.zig"); const Textures = @import("Textures.zig");
allocator: std.mem.Allocator, const MaterialBuffer = GenericBuffer(void, Material);
map: Map, map: Map,
storage_buffer: StorageBuffer, material_buffer: MaterialBuffer,
next_id: Id, next_id: Id,
pub const initial_capacity = 4; // capacity * @sizeOf(Material) = 832 kiB
pub const capacity = std.math.maxInt(std.meta.Tag(Id));
pub const Id = extern struct { pub const Key = struct { atom: atoms.Atom };
id: u32, pub const Id = enum(u16) {
_,
pub fn next(self: Id) Id {
return @enumFromInt(@intFromEnum(self) + 1);
}
}; };
pub const Map = std.AutoHashMapUnmanaged(Atoms.Atom, Id); pub const Map = std.AutoHashMapUnmanaged(Key, Id);
pub const Material = extern struct { pub const Material = extern struct {
base_color: [3]f32, base_color: [3]f32,
base_color_texture: Textures.Id,
emissive: [3]f32, emissive: [3]f32,
emissive_texture: Textures.Id,
ior: f32, ior: f32,
metallic: f32, metallic: f32,
normal_scale: f32, normal_scale: f32,
normal_texture: Textures.Id,
occlusion_roughness_metallic_texture: Textures.Id,
occlusion_texture_strength: f32, occlusion_texture_strength: f32,
roughness: f32, roughness: f32,
base_color_texture: Textures.Id,
emissive_texture: Textures.Id,
normal_texture: Textures.Id,
occlusion_roughness_metallic_texture: Textures.Id,
}; };
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Materials { pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Materials {
var map: Map = .empty; var map: Map = .empty;
errdefer map.deinit(allocator); errdefer map.deinit(allocator);
try map.ensureTotalCapacity(allocator, capacity);
var storage_buffer: StorageBuffer = try .init(engine, void, Material, initial_capacity); var material_buffer = try MaterialBuffer.init(engine, .{
errdefer storage_buffer.deinit(engine); .usage = .storage,
.target_queue = .graphics,
.array_capacity = capacity,
});
errdefer material_buffer.deinit(engine);
return .{ return .{
.allocator = allocator,
.map = map, .map = map,
.storage_buffer = storage_buffer, .material_buffer = material_buffer,
.next_id = .{ .id = 0 }, .next_id = @enumFromInt(0),
}; };
} }
pub fn deinit(self: *Materials, engine: *Engine) void { pub fn deinit(self: *Materials, engine: *Engine, allocator: std.mem.Allocator) void {
self.storage_buffer.deinit(engine); self.material_buffer.deinit(engine);
self.map.deinit(self.allocator); self.map.deinit(allocator);
self.* = undefined; self.* = undefined;
} }
pub fn getId(self: *const Materials, key: Atoms.Atom) ?Id { pub fn getAtom(self: *const Materials, atom: atoms.Atom) ?Id {
const key: Key = .{ .atom = atom };
return self.map.get(key); return self.map.get(key);
} }
pub fn getOrLoadId(self: *Materials, engine: *Engine, textures: *Textures, atoms: *Atoms, key: Atoms.Atom) !Id { pub fn getFilename(self: *const Materials, filename: []const u8) ?Id {
const entry = try self.map.getOrPut(self.allocator, key); const atom = atoms.getAtom(filename) orelse return null;
const key: Key = .{ .atom = atom };
return self.map.get(key);
}
pub fn getOrLoadAtom(self: *Materials, engine: *Engine, textures: *Textures, atom: atoms.Atom, temp_allocator: std.mem.Allocator) !Id {
const key: Key = .{ .atom = atom };
const entry = self.map.getOrPutAssumeCapacity(key);
if (entry.found_existing) { if (entry.found_existing) {
return entry.value_ptr.*; return entry.value_ptr.*;
} else { } else {
const id = try self.loadMaterial(engine, textures, atoms, key); errdefer _ = self.map.remove(key);
const id = try self.loadMaterial(engine, textures, atoms.getString(atom), temp_allocator);
entry.value_ptr.* = id; entry.value_ptr.* = id;
return id; return id;
} }
} }
fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *Atoms, key: Atoms.Atom) !Id { pub fn getOrLoadFilename(self: *Materials, engine: *Engine, textures: *Textures, filename: []const u8, temp_allocator: std.mem.Allocator) !Id {
const atom = try atoms.getOrPutAtom(filename);
const key: Key = .{ .atom = atom };
const entry = self.map.getOrPutAssumeCapacity(key);
if (entry.found_existing) {
return entry.value_ptr.*;
} else {
errdefer _ = self.map.remove(key);
const id = try self.loadMaterial(engine, textures, filename, temp_allocator);
entry.value_ptr.* = id;
return id;
}
}
fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, filename: []const u8, temp_allocator: std.mem.Allocator) !Id {
const MaterialJson = struct { const MaterialJson = struct {
baseColor: [3]f32 = .{ 1, 1, 1 }, baseColor: [3]f32 = .{ 1, 1, 1 },
baseColorTexture: ?[]const u8 = null, baseColorTexture: ?[]const u8 = null,
@@ -87,7 +123,6 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *
roughness: f32 = 1, roughness: f32 = 1,
}; };
const filename = atoms.getString(key).?;
std.log.debug("Loading material \"{s}\"...", .{filename}); std.log.debug("Loading material \"{s}\"...", .{filename});
const cwd = std.fs.cwd(); const cwd = std.fs.cwd();
@@ -102,10 +137,10 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *
defer file.close(); defer file.close();
var file_reader = file.reader(&buffer); var file_reader = file.reader(&buffer);
var json_reader: std.json.Reader = .init(self.allocator, &file_reader.interface); var json_reader: std.json.Reader = .init(temp_allocator, &file_reader.interface);
defer json_reader.deinit(); defer json_reader.deinit();
const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, self.allocator, &json_reader, .{ const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, temp_allocator, &json_reader, .{
.duplicate_field_behavior = .@"error", .duplicate_field_behavior = .@"error",
.ignore_unknown_fields = false, .ignore_unknown_fields = false,
.allocate = .alloc_if_needed, .allocate = .alloc_if_needed,
@@ -116,65 +151,57 @@ fn loadMaterial(self: *Materials, engine: *Engine, textures: *Textures, atoms: *
const base_color_texture = blk: { const base_color_texture = blk: {
if (material_json.baseColorTexture) |name| { if (material_json.baseColorTexture) |name| {
const atom = try atoms.getOrPutAtom(name); break :blk try textures.getOrLoadFilename(engine, name, .base_color, temp_allocator);
break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .base_color });
} else { } else {
break :blk textures.empty_base_color; break :blk .empty_base_color;
} }
}; };
const emissive_texture = blk: { const emissive_texture = blk: {
if (material_json.emissiveTexture) |name| { if (material_json.emissiveTexture) |name| {
const atom = try atoms.getOrPutAtom(name); break :blk try textures.getOrLoadFilename(engine, name, .emissive, temp_allocator);
break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .emissive });
} else { } else {
break :blk textures.empty_emissive; break :blk .empty_emissive;
} }
}; };
const normal_texture = blk: { const normal_texture = blk: {
if (material_json.normalTexture) |name| { if (material_json.normalTexture) |name| {
const atom = try atoms.getOrPutAtom(name); break :blk try textures.getOrLoadFilename(engine, name, .normal, temp_allocator);
break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .normal });
} else { } else {
break :blk textures.empty_normal; break :blk .empty_normal;
} }
}; };
const occlusion_roughness_metallic_texture = blk: { const occlusion_roughness_metallic_texture = blk: {
if (material_json.occlusionRoughnessMetallicTexture) |name| { if (material_json.occlusionRoughnessMetallicTexture) |name| {
const atom = try atoms.getOrPutAtom(name); break :blk try textures.getOrLoadFilename(engine, name, .occlusion_roughness_metallic, temp_allocator);
break :blk try textures.getOrLoadId(engine, atoms, .{ .atom = atom, .usage = .occlusion_roughness_metallic });
} else { } else {
break :blk textures.empty_occlusuion_roughness_metallic; break :blk .empty_occlusion_roughness_metallic;
} }
}; };
const next_id = self.next_id; try self.material_buffer.write(engine, .{
const offset: usize = next_id.id; .element_offset = @intFromEnum(self.next_id),
.elements = &.{
if (offset >= self.storage_buffer.array_capacity) { .{
try self.storage_buffer.enlarge(void, Material, engine, self.storage_buffer.array_capacity * 2); .base_color = material_json.baseColor,
} .emissive = material_json.emissive,
.ior = material_json.ior,
const materials = [_]Material{ .metallic = material_json.metallic,
.{ .normal_scale = material_json.normalScale,
.base_color = material_json.baseColor, .occlusion_texture_strength = material_json.occlusionTextureStrength,
.base_color_texture = base_color_texture, .roughness = material_json.roughness,
.emissive = material_json.emissive, .base_color_texture = base_color_texture,
.emissive_texture = emissive_texture, .emissive_texture = emissive_texture,
.ior = material_json.ior, .normal_texture = normal_texture,
.metallic = material_json.metallic, .occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture,
.normal_scale = material_json.normalScale, },
.normal_texture = normal_texture,
.occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture,
.occlusion_texture_strength = material_json.occlusionTextureStrength,
.roughness = material_json.roughness,
}, },
}; });
try self.storage_buffer.writeOffset(void, Material, engine, {}, offset, &materials); const id = self.next_id;
self.next_id = .{ .id = next_id.id + 1 }; self.next_id = self.next_id.next();
return next_id; return id;
} }

View File

@@ -3,36 +3,41 @@ const std = @import("std");
const stbi = @import("zstbi"); const stbi = @import("zstbi");
const Atoms = @import("Atoms.zig"); const atoms = @import("../engine/atoms.zig");
const Engine = @import("../engine/Engine.zig"); const Engine = @import("../engine/Engine.zig");
const Texture = @import("../engine/Texture.zig"); const Texture = @import("../engine/Texture.zig");
allocator: std.mem.Allocator,
map: Map, map: Map,
array: Array, textures: Array,
empty_base_color: Id, pub const capacity = std.math.maxInt(std.meta.Tag(Id));
empty_emissive: Id,
empty_normal: Id,
empty_occlusuion_roughness_metallic: Id,
pub const Id = extern struct {
id: u32,
};
pub const Key = struct { pub const Key = struct {
atom: Atoms.Atom, atom: atoms.Atom,
usage: Texture.Usage, usage: Texture.Usage,
}; };
pub const Id = enum(u16) {
empty_base_color = 0,
empty_emissive = 1,
empty_normal = 2,
empty_occlusion_roughness_metallic = 3,
_,
pub fn next(self: Id) Id {
return @enumFromInt(@intFromEnum(self) + 1);
}
};
pub const Map = std.AutoHashMapUnmanaged(Key, Id); pub const Map = std.AutoHashMapUnmanaged(Key, Id);
pub const Array = std.ArrayList(Texture); pub const Array = std.ArrayList(Texture);
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures {
var map: Map = .empty; var map: Map = .empty;
errdefer map.deinit(allocator); errdefer map.deinit(allocator);
try map.ensureTotalCapacity(allocator, capacity);
var array: Array = try .initCapacity(allocator, 4); var array: Array = try .initCapacity(allocator, capacity);
errdefer { errdefer {
for (array.items) |*texture| { for (array.items) |*texture| {
texture.deinit(engine); texture.deinit(engine);
@@ -40,79 +45,116 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures {
array.deinit(allocator); array.deinit(allocator);
} }
const empty_base_color: Id = .{ .id = @intCast(array.items.len) }; const empty_base_color_texture = try Texture.init(engine, .{
const empty_base_color_texture: Texture = try .init(engine, 1, 1, .base_color); .width = 1,
array.appendAssumeCapacity(empty_base_color_texture); .height = 1,
.usage = .base_color,
.target_queue = .graphics,
});
const empty_emissive: Id = .{ .id = @intCast(array.items.len) }; const empty_emissive_texture = try Texture.init(engine, .{
const empty_emissive_texture: Texture = try .init(engine, 1, 1, .emissive); .width = 1,
array.appendAssumeCapacity(empty_emissive_texture); .height = 1,
.usage = .emissive,
.target_queue = .graphics,
});
const empty_normal: Id = .{ .id = @intCast(array.items.len) }; const empty_normal_texture = try Texture.init(engine, .{
const empty_normal_texture: Texture = try .init(engine, 1, 1, .normal); .width = 1,
array.appendAssumeCapacity(empty_normal_texture); .height = 1,
.usage = .normal,
.target_queue = .graphics,
});
const empty_occlusuion_roughness_metallic: Id = .{ .id = @intCast(array.items.len) }; const empty_occlusuion_roughness_metallic_texture = try Texture.init(engine, .{
const empty_occlusuion_roughness_metallic_texture: Texture = try .init(engine, 1, 1, .occlusion_roughness_metallic); .width = 1,
array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); .height = 1,
.usage = .occlusion_roughness_metallic,
.target_queue = .graphics,
});
try empty_base_color_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); array.appendSliceAssumeCapacity(&.{
try empty_emissive_texture.write([4]f16, engine, &.{.{ 1.0, 1.0, 1.0, 1.0 }}); empty_base_color_texture,
try empty_normal_texture.write([4]i8, engine, &.{.{ 0, 0, 127, 127 }}); empty_emissive_texture,
try empty_occlusuion_roughness_metallic_texture.write([4]u8, engine, &.{.{ 255, 255, 255, 255 }}); empty_normal_texture,
empty_occlusuion_roughness_metallic_texture,
});
try empty_base_color_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 });
try empty_emissive_texture.writeSamples(f16, engine, &.{ 1.0, 1.0, 1.0, 1.0 });
try empty_normal_texture.writeSamples(i8, engine, &.{ 0, 0, 127, 127 });
try empty_occlusuion_roughness_metallic_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 });
return .{ return .{
.allocator = allocator,
.map = map, .map = map,
.array = array, .textures = array,
.empty_base_color = empty_base_color,
.empty_emissive = empty_emissive,
.empty_normal = empty_normal,
.empty_occlusuion_roughness_metallic = empty_occlusuion_roughness_metallic,
}; };
} }
pub fn deinit(self: *Textures, engine: *Engine) void { pub fn deinit(self: *Textures, engine: *Engine, allocator: std.mem.Allocator) void {
for (self.array.items) |*texture| { for (self.textures.items) |*texture| {
texture.deinit(engine); texture.deinit(engine);
} }
self.array.deinit(self.allocator); self.textures.deinit(allocator);
self.map.deinit(allocator);
self.* = undefined;
}
self.map.deinit(self.allocator); pub fn getAtom(self: *const Textures, atom: atoms.Atom, usage: Texture.Usage) ?Id {
const key: Key = .{ .atom = atom, .usage = usage };
return self.map.get(key);
}
pub fn getFilename(self: *const Textures, filename: []const u8, usage: Texture.Usage) ?Id {
const atom = atoms.getAtom(filename) orelse return null;
const key: Key = .{ .atom = atom, .usage = usage };
return self.map.get(key);
} }
pub fn getTexture(self: *const Textures, id: Id) ?*Texture { pub fn getTexture(self: *const Textures, id: Id) ?*Texture {
const index: usize = id.id; const index: usize = id.id;
return if (index < self.array.items.len) &self.array.items[index] else null; return if (index < self.textures.items.len) &self.textures.items[index] else null;
} }
pub fn getId(self: *const Textures, key: Key) ?Id { pub fn getId(self: *const Textures, key: Key) ?Id {
return self.map.get(key); return self.map.get(key);
} }
pub fn getOrLoadId(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Id { pub fn getOrLoadAtom(self: *Textures, engine: *Engine, atom: atoms.Atom, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id {
const entry = try self.map.getOrPut(self.allocator, key); const key: Key = .{ .atom = atom, .usage = usage };
const entry = self.map.getOrPutAssumeCapacity(key);
if (entry.found_existing) { if (entry.found_existing) {
return entry.value_ptr.*; return entry.value_ptr.*;
} else { } else {
errdefer _ = self.map.remove(key); errdefer _ = self.map.remove(key);
try self.array.ensureUnusedCapacity(self.allocator, 1); const texture = try self.loadTexture(engine, atoms.getString(atom), usage, temp_allocator);
const texture = try self.loadTexture(engine, atoms, key);
const id = self.nextId(); const id = self.nextId();
entry.value_ptr.* = id; entry.value_ptr.* = id;
self.array.appendAssumeCapacity(texture); self.textures.appendAssumeCapacity(texture);
return id; return id;
} }
} }
fn loadTexture(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Texture { pub fn getOrLoadFilename(self: *Textures, engine: *Engine, filename: []const u8, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id {
const filename = atoms.getString(key.atom).?; const atom = try atoms.getOrPutAtom(filename);
std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(key.usage) }); const key: Key = .{ .atom = atom, .usage = usage };
const entry = self.map.getOrPutAssumeCapacity(key);
if (entry.found_existing) {
return entry.value_ptr.*;
} else {
errdefer _ = self.map.remove(key);
const texture = try loadTexture(engine, filename, usage, temp_allocator);
const id = self.nextId();
entry.value_ptr.* = id;
self.textures.appendAssumeCapacity(texture);
return id;
}
}
fn loadTexture(engine: *Engine, filename: []const u8, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Texture {
std.log.debug("Loading texture \"{s}\" as {s}...", .{ filename, @tagName(usage) });
const cwd = std.fs.cwd(); const cwd = std.fs.cwd();
@@ -122,22 +164,26 @@ fn loadTexture(self: *Textures, engine: *Engine, atoms: *Atoms, key: Key) !Textu
const file = try dir.openFile(filename, .{}); const file = try dir.openFile(filename, .{});
defer file.close(); defer file.close();
const file_buf = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize)); const file_buf = try file.readToEndAlloc(temp_allocator, std.math.maxInt(usize));
defer self.allocator.free(file_buf); defer temp_allocator.free(file_buf);
var img = try stbi.Image.loadFromMemory(file_buf, key.usage.sampleCount()); var img = try stbi.Image.loadFromMemory(file_buf, usage.samplesPerTexel());
defer img.deinit(); defer img.deinit();
std.debug.assert(img.num_components == key.usage.sampleCount()); std.debug.assert(img.num_components == usage.samplesPerTexel());
var texture: Texture = try .init(engine, img.width, img.height, key.usage); var texture = try Texture.init(engine, .{
.width = img.width,
.height = img.height,
.usage = usage,
.target_queue = .graphics,
});
errdefer texture.deinit(engine); errdefer texture.deinit(engine);
try texture.write(u8, engine, img.data); try texture.writeRaw(engine, img.data);
return texture; return texture;
} }
fn nextId(self: *const Textures) Id { fn nextId(self: *const Textures) Id {
const index = self.array.items.len; const index = self.textures.items.len;
return .{ .id = @intCast(index) }; return @enumFromInt(index);
} }

View File

@@ -281,7 +281,17 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine {
try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name); try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name);
} }
const enabled_features_vulkan10: vk.PhysicalDeviceFeatures = .{
.shader_int_16 = .true,
};
var enabled_features_vulkan11: vk.PhysicalDeviceVulkan11Features = .{
.storage_buffer_16_bit_access = .true,
.uniform_and_storage_buffer_16_bit_access = .true,
};
var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{ var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{
.p_next = &enabled_features_vulkan11,
.descriptor_binding_partially_bound = .true, .descriptor_binding_partially_bound = .true,
.descriptor_binding_variable_descriptor_count = .true, .descriptor_binding_variable_descriptor_count = .true,
.runtime_descriptor_array = .true, .runtime_descriptor_array = .true,
@@ -294,6 +304,7 @@ pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine {
.p_queue_create_infos = queue_create_info.items.ptr, .p_queue_create_infos = queue_create_info.items.ptr,
.enabled_extension_count = @intCast(enabled_extensions.items.len), .enabled_extension_count = @intCast(enabled_extensions.items.len),
.pp_enabled_extension_names = enabled_extensions.items.ptr, .pp_enabled_extension_names = enabled_extensions.items.ptr,
.p_enabled_features = &enabled_features_vulkan10,
}, &vk_allocator.interface); }, &vk_allocator.interface);
}; };
@@ -561,3 +572,541 @@ fn findPresentationQueueFamily(queue_families_properties: []const vk.QueueFamily
return null; return null;
} }
// --- VULKAN WRAPPERS ---------------------------------------------------------
pub const BufferCreateInfo = struct {
flags: vk.BufferCreateFlags = .{},
size: vk.DeviceSize,
usage: vk.BufferUsageFlags,
queue_family_indices: []const u32 = &.{},
};
pub const DescriptorPoolCreateInfo = struct {
flags: vk.DescriptorPoolCreateFlags = .{},
max_sets: u32,
pool_sizes: []const vk.DescriptorPoolSize = &.{},
};
pub const DescriptorSetAllocateInfo = struct {
descriptor_pool: vk.DescriptorPool,
set_layouts: []const vk.DescriptorSetLayout,
variable_descriptor_counts: []const u32 = &.{},
};
pub const DescriptorSetLayoutBinding = struct {
binding: u32,
descriptor_type: vk.DescriptorType,
descriptor_count: u32,
stage_flags: vk.ShaderStageFlags,
immutable_samplers: []const vk.Sampler = &.{},
flags: vk.DescriptorBindingFlags = .{},
};
pub const DescriptorSetLayoutCreateInfo = struct {
flags: vk.DescriptorSetLayoutCreateFlags = .{},
bindings: []const DescriptorSetLayoutBinding = &.{},
};
pub const DescriptorSetsUpdateInfo = struct {
writes: []const WriteDescriptorSet = &.{},
copies: []const vk.CopyDescriptorSet = &.{},
};
pub const FramebufferCreateInfo = struct {
flags: vk.FramebufferCreateFlags = .{},
render_pass: vk.RenderPass,
attachments: []const vk.ImageView = &.{},
width: u32,
height: u32,
layers: u32,
};
pub const GraphicsPipelineCreateInfo = struct {
flags: vk.PipelineCreateFlags = .{},
stages: []const PipelineShaderStageCreateInfo = &.{},
vertex_input_state: ?PipelineVertexInputStateCreateInfo = null,
input_assembly_state: ?vk.PipelineInputAssemblyStateCreateInfo = null,
tessellation_state: ?vk.PipelineTessellationStateCreateInfo = null,
viewport_state: ?PipelineViewportStateCreateInfo = null,
rasterization_state: ?vk.PipelineRasterizationStateCreateInfo = null,
multisample_state: ?vk.PipelineMultisampleStateCreateInfo = null,
depth_stencil_state: ?vk.PipelineDepthStencilStateCreateInfo = null,
color_blend_state: ?PipelineColorBlendStateCreateInfo = null,
dynamic_state: ?PipelineDynamicStateCreateInfo = null,
layout: vk.PipelineLayout = .null_handle,
render_pass: vk.RenderPass = .null_handle,
subpass: u32,
base_pipeline_handle: vk.Pipeline = .null_handle,
};
pub const ImageCreateInfo = struct {
flags: vk.ImageCreateFlags = .{},
image_type: vk.ImageType,
format: vk.Format,
extent: vk.Extent3D,
mip_levels: u32,
array_layers: u32,
samples: vk.SampleCountFlags,
tiling: vk.ImageTiling,
usage: vk.ImageUsageFlags,
queue_family_indices: []const u32 = &.{},
initial_layout: vk.ImageLayout,
};
pub const ImageViewCreateInfo = struct {
flags: vk.ImageViewCreateFlags = .{},
image: vk.Image,
view_type: vk.ImageViewType,
format: vk.Format,
components: vk.ComponentMapping = .{
.r = .identity,
.g = .identity,
.b = .identity,
.a = .identity,
},
subresource_range: vk.ImageSubresourceRange,
};
pub const PipelineColorBlendStateCreateInfo = struct {
flags: vk.PipelineColorBlendStateCreateFlags = .{},
logic_op_enable: vk.Bool32,
logic_op: vk.LogicOp,
attachments: []const vk.PipelineColorBlendAttachmentState = &.{},
blend_constants: [4]f32,
};
pub const PipelineDynamicStateCreateInfo = struct {
flags: vk.PipelineDynamicStateCreateFlags = .{},
dynamic_states: []const vk.DynamicState = &.{},
};
pub const PipelineLayoutCreateInfo = struct {
flags: vk.PipelineLayoutCreateFlags = .{},
set_layouts: []const vk.DescriptorSetLayout = &.{},
push_constant_ranges: []const vk.PushConstantRange = &.{},
};
pub const PipelineShaderStageCreateInfo = struct {
flags: vk.PipelineShaderStageCreateFlags = .{},
stage: vk.ShaderStageFlags,
module: vk.ShaderModule = .null_handle,
name: [*:0]const u8,
specialization_info: ?SpecializationInfo = null,
};
pub const PipelineVertexInputStateCreateInfo = struct {
flags: vk.PipelineVertexInputStateCreateFlags = .{},
vertex_binding_descriptions: []const vk.VertexInputBindingDescription = &.{},
vertex_attribute_descriptions: []const vk.VertexInputAttributeDescription = &.{},
};
pub const PipelineViewportStateCreateInfo = struct {
flags: vk.PipelineViewportStateCreateFlags = .{},
viewports: []const vk.Viewport = &.{},
scissors: []const vk.Rect2D = &.{},
};
pub const ShaderModuleCreateInfo = struct {
flags: vk.ShaderModuleCreateFlags = .{},
code: []align(@alignOf(u32)) const u8,
};
pub const SpecializationInfo = struct {
map_entries: []vk.SpecializationMapEntry = &.{},
data: []const u8 = &.{},
};
pub const WriteDescriptorSet = struct {
dst_set: vk.DescriptorSet,
dst_binding: u32,
dst_array_element: u32,
descriptor_type: vk.DescriptorType,
descriptor_infos: union(enum) {
image: []const vk.DescriptorImageInfo,
buffer: []const vk.DescriptorBufferInfo,
texel_buffer_view: []const vk.BufferView,
},
};
pub fn createBuffer(self: *Engine, create_info: BufferCreateInfo) !vk.Buffer {
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{};
for (create_info.queue_family_indices) |queue_family_index| {
try queue_family_indices_set.put(allocator, queue_family_index, {});
}
const queue_family_indices = queue_family_indices_set.keys();
const buffer = self.device.createBuffer(&.{
.flags = create_info.flags,
.size = create_info.size,
.usage = create_info.usage,
.sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive,
.queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0,
.p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null,
}, &self.vk_allocator.interface);
return buffer;
}
pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void {
std.debug.assert(descriptor_sets.len >= allocate_info.set_layouts.len);
const has_variable_descriptor_counts = allocate_info.variable_descriptor_counts.len > 0;
var p_next: ?*const anyopaque = null;
if (has_variable_descriptor_counts) {
p_next = &vk.DescriptorSetVariableDescriptorCountAllocateInfo{
.p_next = p_next,
.descriptor_set_count = @intCast(allocate_info.variable_descriptor_counts.len),
.p_descriptor_counts = allocate_info.variable_descriptor_counts.ptr,
};
}
try self.device.allocateDescriptorSets(&.{
.p_next = p_next,
.descriptor_pool = allocate_info.descriptor_pool,
.descriptor_set_count = @intCast(allocate_info.set_layouts.len),
.p_set_layouts = allocate_info.set_layouts.ptr,
}, descriptor_sets.ptr);
}
pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayoutCreateInfo) !vk.DescriptorSetLayout {
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const has_binding_flags = blk: {
for (create_info.bindings) |binding| {
if (@as(vk.Flags, @bitCast(binding.flags)) != 0) {
break :blk true;
}
}
break :blk false;
};
var p_next: ?*const anyopaque = null;
if (has_binding_flags) {
const descriptor_set_layout_bindings_flags = try allocator.alloc(vk.DescriptorBindingFlags, create_info.bindings.len);
for (descriptor_set_layout_bindings_flags, create_info.bindings) |*x, binding| {
x.* = binding.flags;
}
p_next = &vk.DescriptorSetLayoutBindingFlagsCreateInfo{
.p_next = p_next,
.binding_count = @intCast(descriptor_set_layout_bindings_flags.len),
.p_binding_flags = descriptor_set_layout_bindings_flags.ptr,
};
}
const bindings = try allocator.alloc(vk.DescriptorSetLayoutBinding, create_info.bindings.len);
for (bindings, create_info.bindings) |*x, binding| {
x.* = .{
.binding = binding.binding,
.descriptor_type = binding.descriptor_type,
.descriptor_count = binding.descriptor_count,
.stage_flags = binding.stage_flags,
.p_immutable_samplers = binding.immutable_samplers.ptr,
};
}
const descriptor_set_layout = try self.device.createDescriptorSetLayout(&.{
.p_next = p_next,
.flags = create_info.flags,
.binding_count = @intCast(bindings.len),
.p_bindings = bindings.ptr,
}, &self.vk_allocator.interface);
return descriptor_set_layout;
}
pub fn createDescriptorPool(self: *Engine, create_info: DescriptorPoolCreateInfo) !vk.DescriptorPool {
const descriptor_pool = try self.device.createDescriptorPool(&.{
.flags = create_info.flags,
.max_sets = create_info.max_sets,
.pool_size_count = @intCast(create_info.pool_sizes.len),
.p_pool_sizes = create_info.pool_sizes.ptr,
}, &self.vk_allocator.interface);
return descriptor_pool;
}
pub fn createFence(self: *Engine, create_info: vk.FenceCreateInfo) !vk.Fence {
const fence = try self.device.createFence(&create_info, &self.vk_allocator.interface);
return fence;
}
pub fn createFramebuffer(self: *Engine, create_info: FramebufferCreateInfo) !vk.Framebuffer {
const framebuffer = try self.device.createFramebuffer(&.{
.flags = create_info.flags,
.render_pass = create_info.render_pass,
.attachment_count = @intCast(create_info.attachments.len),
.p_attachments = create_info.attachments.ptr,
.width = create_info.width,
.height = create_info.height,
.layers = create_info.layers,
}, &self.vk_allocator.interface);
return framebuffer;
}
pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreateInfo) !vk.Pipeline {
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const stages = try allocator.alloc(vk.PipelineShaderStageCreateInfo, create_info.stages.len);
for (stages, create_info.stages) |*x, *stage| {
x.* = .{
.flags = stage.flags,
.stage = stage.stage,
.p_name = stage.name,
.module = stage.module,
.p_specialization_info = blk: {
if (stage.specialization_info) |y| {
const specialization_info = try allocator.create(vk.SpecializationInfo);
specialization_info.* = .{
.map_entry_count = @intCast(y.map_entries.len),
.p_map_entries = y.map_entries.ptr,
.data_size = y.data.len,
.p_data = y.data.ptr,
};
break :blk specialization_info;
} else {
break :blk null;
}
},
};
}
const graphics_pipeline_create_infos = [_]vk.GraphicsPipelineCreateInfo{
.{
.flags = create_info.flags,
.stage_count = @intCast(stages.len),
.p_stages = stages.ptr,
.p_vertex_input_state = if (create_info.vertex_input_state) |vertex_input_state| &.{
.flags = vertex_input_state.flags,
.vertex_binding_description_count = @intCast(vertex_input_state.vertex_binding_descriptions.len),
.p_vertex_binding_descriptions = vertex_input_state.vertex_binding_descriptions.ptr,
.vertex_attribute_description_count = @intCast(vertex_input_state.vertex_attribute_descriptions.len),
.p_vertex_attribute_descriptions = vertex_input_state.vertex_attribute_descriptions.ptr,
} else null,
.p_input_assembly_state = if (create_info.input_assembly_state) |*x| x else null,
.p_tessellation_state = if (create_info.tessellation_state) |*x| x else null,
.p_viewport_state = if (create_info.viewport_state) |viewport_state| &.{
.flags = viewport_state.flags,
.viewport_count = @intCast(viewport_state.viewports.len),
.p_viewports = viewport_state.viewports.ptr,
.scissor_count = @intCast(viewport_state.scissors.len),
.p_scissors = viewport_state.scissors.ptr,
} else null,
.p_rasterization_state = if (create_info.rasterization_state) |*x| x else null,
.p_multisample_state = if (create_info.multisample_state) |*x| x else null,
.p_depth_stencil_state = if (create_info.depth_stencil_state) |*x| x else null,
.p_color_blend_state = if (create_info.color_blend_state) |color_blend_state| &.{
.flags = color_blend_state.flags,
.logic_op_enable = color_blend_state.logic_op_enable,
.logic_op = color_blend_state.logic_op,
.attachment_count = @intCast(color_blend_state.attachments.len),
.p_attachments = color_blend_state.attachments.ptr,
.blend_constants = color_blend_state.blend_constants,
} else null,
.p_dynamic_state = if (create_info.dynamic_state) |dynamic_state| &.{
.flags = dynamic_state.flags,
.dynamic_state_count = @intCast(dynamic_state.dynamic_states.len),
.p_dynamic_states = dynamic_state.dynamic_states.ptr,
} else null,
.layout = create_info.layout,
.render_pass = create_info.render_pass,
.subpass = create_info.subpass,
.base_pipeline_handle = create_info.base_pipeline_handle,
.base_pipeline_index = -1,
},
};
var pipelines: [1]vk.Pipeline = undefined;
_ = try self.device.createGraphicsPipelines(.null_handle, graphics_pipeline_create_infos.len, &graphics_pipeline_create_infos, &self.vk_allocator.interface, &pipelines);
return pipelines[0];
}
pub fn createImage(self: *Engine, create_info: ImageCreateInfo) !vk.Image {
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
defer arena.deinit();
const allocator = arena.allocator();
var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{};
for (create_info.queue_family_indices) |queue_family_index| {
try queue_family_indices_set.put(allocator, queue_family_index, {});
}
const queue_family_indices = queue_family_indices_set.keys();
const image = self.device.createImage(&.{
.flags = create_info.flags,
.image_type = create_info.image_type,
.format = create_info.format,
.extent = create_info.extent,
.mip_levels = create_info.mip_levels,
.array_layers = create_info.array_layers,
.samples = create_info.samples,
.tiling = create_info.tiling,
.usage = create_info.usage,
.sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive,
.queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0,
.p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null,
.initial_layout = create_info.initial_layout,
}, &self.vk_allocator.interface);
return image;
}
pub fn createImageView(self: *Engine, create_info: ImageViewCreateInfo) !vk.ImageView {
const image_view = try self.device.createImageView(&.{
.flags = create_info.flags,
.image = create_info.image,
.view_type = create_info.view_type,
.format = create_info.format,
.components = create_info.components,
.subresource_range = create_info.subresource_range,
}, &self.vk_allocator.interface);
return image_view;
}
pub fn createPipelineLayout(self: *Engine, create_info: PipelineLayoutCreateInfo) !vk.PipelineLayout {
const pipeline_layout = try self.device.createPipelineLayout(&.{
.flags = create_info.flags,
.set_layout_count = @intCast(create_info.set_layouts.len),
.p_set_layouts = create_info.set_layouts.ptr,
.push_constant_range_count = @intCast(create_info.push_constant_ranges.len),
.p_push_constant_ranges = create_info.push_constant_ranges.ptr,
}, &self.vk_allocator.interface);
return pipeline_layout;
}
pub fn createSampler(self: *Engine, create_info: vk.SamplerCreateInfo) !vk.Sampler {
const sampler = try self.device.createSampler(&create_info, &self.vk_allocator.interface);
return sampler;
}
pub fn createSemaphore(self: *Engine) !vk.Semaphore {
const semaphore = try self.device.createSemaphore(&.{}, &self.vk_allocator.interface);
return semaphore;
}
pub fn createShaderModule(self: *Engine, create_info: ShaderModuleCreateInfo) !vk.ShaderModule {
const shader_module = try self.device.createShaderModule(&.{
.flags = create_info.flags,
.code_size = create_info.code.len,
.p_code = @ptrCast(create_info.code.ptr),
}, &self.vk_allocator.interface);
return shader_module;
}
pub fn destroyBuffer(self: *Engine, buffer: vk.Buffer) void {
self.device.destroyBuffer(buffer, &self.vk_allocator.interface);
}
pub fn destroyDescriptorPool(self: *Engine, descriptor_pool: vk.DescriptorPool) void {
self.device.destroyDescriptorPool(descriptor_pool, &self.vk_allocator.interface);
}
pub fn destroyDescriptorSetLayout(self: *Engine, descriptor_set_layout: vk.DescriptorSetLayout) void {
self.device.destroyDescriptorSetLayout(descriptor_set_layout, &self.vk_allocator.interface);
}
pub fn destroyFence(self: *Engine, fence: vk.Fence) void {
self.device.destroyFence(fence, &self.vk_allocator.interface);
}
pub fn destroyFramebuffer(self: *Engine, framebuffer: vk.Framebuffer) void {
self.device.destroyFramebuffer(framebuffer, &self.vk_allocator.interface);
}
pub fn destroyImage(self: *Engine, image: vk.Image) void {
self.device.destroyImage(image, &self.vk_allocator.interface);
}
pub fn destroyImageView(self: *Engine, image_view: vk.ImageView) void {
self.device.destroyImageView(image_view, &self.vk_allocator.interface);
}
pub fn destroyPipeline(self: *Engine, pipeline: vk.Pipeline) void {
self.device.destroyPipeline(pipeline, &self.vk_allocator.interface);
}
pub fn destroyPipelineLayout(self: *Engine, pipeline_layout: vk.PipelineLayout) void {
self.device.destroyPipelineLayout(pipeline_layout, &self.vk_allocator.interface);
}
pub fn destroySampler(self: *Engine, sampler: vk.Sampler) void {
self.device.destroySampler(sampler, &self.vk_allocator.interface);
}
pub fn destroySemaphore(self: *Engine, semaphore: vk.Semaphore) void {
self.device.destroySemaphore(semaphore, &self.vk_allocator.interface);
}
pub fn destroyShaderModule(self: *Engine, shader_module: vk.ShaderModule) void {
self.device.destroyShaderModule(shader_module, &self.vk_allocator.interface);
}
pub fn freeMemory(self: *Engine, device_memory: vk.DeviceMemory) void {
self.device.freeMemory(device_memory, &self.vk_allocator.interface);
}
pub fn resetFence(self: *Engine, fence: vk.Fence) !void {
try self.device.resetFences(1, @ptrCast(&fence));
}
pub fn updateDescriptorSets(self: *Engine, update_info: DescriptorSetsUpdateInfo) !void {
var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const descriptor_writes = try allocator.alloc(vk.WriteDescriptorSet, update_info.writes.len);
for (descriptor_writes, update_info.writes) |*x, write| {
x.* = switch (write.descriptor_infos) {
.image => |y| .{
.dst_set = write.dst_set,
.dst_binding = write.dst_binding,
.dst_array_element = write.dst_array_element,
.descriptor_count = @intCast(y.len),
.descriptor_type = write.descriptor_type,
.p_image_info = y.ptr,
.p_buffer_info = &.{},
.p_texel_buffer_view = &.{},
},
.buffer => |y| .{
.dst_set = write.dst_set,
.dst_binding = write.dst_binding,
.dst_array_element = write.dst_array_element,
.descriptor_count = @intCast(y.len),
.descriptor_type = write.descriptor_type,
.p_image_info = &.{},
.p_buffer_info = y.ptr,
.p_texel_buffer_view = &.{},
},
.texel_buffer_view => |y| .{
.dst_set = write.dst_set,
.dst_binding = write.dst_binding,
.dst_array_element = write.dst_array_element,
.descriptor_count = @intCast(y.len),
.descriptor_type = write.descriptor_type,
.p_image_info = &.{},
.p_buffer_info = &.{},
.p_texel_buffer_view = y.ptr,
},
};
}
self.device.updateDescriptorSets(
@intCast(descriptor_writes.len),
descriptor_writes.ptr,
@intCast(update_info.copies.len),
update_info.copies.ptr,
);
}
pub fn waitForFence(self: *Engine, fence: vk.Fence) !void {
_ = try self.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
}

View File

@@ -0,0 +1,210 @@
const std = @import("std");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig");
const TargetQueue = @import("TargetQueue.zig").TargetQueue;
pub const Usage = enum {
uniform,
storage,
index,
vertex,
indirect,
pub fn asBufferUsageFlags(self: Usage) vk.BufferUsageFlags {
return switch (self) {
.uniform => .{ .transfer_dst_bit = true, .uniform_buffer_bit = true },
.storage => .{ .transfer_dst_bit = true, .storage_buffer_bit = true },
.index => .{ .transfer_dst_bit = true, .index_buffer_bit = true },
.vertex => .{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
.indirect => .{ .transfer_dst_bit = true, .indirect_buffer_bit = true },
};
}
};
pub const InitInfo = struct {
usage: Usage,
target_queue: TargetQueue,
array_capacity: u32 = 0,
};
pub fn GenericBuffer(comptime Header: type, comptime Element: type) type {
return struct {
const Self = @This();
buffer: vk.Buffer,
device_memory: vk.DeviceMemory,
target_queue: TargetQueue,
array_capacity: u32,
const header_size: u32 = @sizeOf(Header);
const element_size: u32 = @sizeOf(Element);
const array_offset: u32 = std.mem.alignForward(usize, header_size, @alignOf(Element));
pub const WriteInfo = struct {
header: ?Header = null,
element_offset: u32 = 0,
elements: []const Element = &.{},
};
pub fn init(engine: *Engine, init_info: InitInfo) !Self {
const array_size = try std.math.mul(u32, init_info.array_capacity, element_size);
const size = try std.math.add(u32, array_offset, array_size);
const target_queue_family = switch (init_info.target_queue) {
.graphics => engine.graphics_queue.allocation.family,
.compute => engine.compute_queue.allocation.family,
};
const transfer_queue_family = engine.transfer_queue.allocation.family;
const buffer = try engine.createBuffer(.{
.size = size,
.usage = init_info.usage.asBufferUsageFlags(),
.queue_family_indices = &.{ target_queue_family, transfer_queue_family },
});
errdefer engine.destroyBuffer(buffer);
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
errdefer engine.freeMemory(device_memory);
try engine.device.bindBufferMemory(buffer, device_memory, 0);
return .{
.buffer = buffer,
.device_memory = device_memory,
.target_queue = init_info.target_queue,
.array_capacity = init_info.array_capacity,
};
}
pub fn deinit(self: *Self, engine: *Engine) void {
engine.freeMemory(self.device_memory);
engine.destroyBuffer(self.buffer);
self.* = undefined;
}
pub fn write(self: Self, engine: *Engine, write_info: WriteInfo) !void {
const element_count = std.math.cast(u32, write_info.elements.len) orelse return error.Overflow;
std.debug.assert(write_info.element_offset + element_count <= self.array_capacity);
const header_write_size: u32 = if (write_info.header != null) header_size else 0;
const array_write_size = element_count * element_size;
const array_write_offset: u32 = if (header_write_size > 0) array_offset else 0;
const write_size = array_write_offset + array_write_size;
if (write_size == 0) {
return;
}
var regions_buffer: [2]vk.BufferCopy = undefined;
var regions: std.ArrayList(vk.BufferCopy) = .initBuffer(&regions_buffer);
// One continuous write
if (header_write_size > 0 and array_write_size > 0 and write_info.element_offset == 0) {
regions.appendAssumeCapacity(.{
.src_offset = 0,
.dst_offset = 0,
.size = write_size,
});
}
// Separate writes for header and array (if they exist)
else {
if (header_write_size > 0) {
regions.appendAssumeCapacity(.{
.src_offset = 0,
.dst_offset = 0,
.size = header_write_size,
});
}
if (array_write_size > 0) {
regions.appendAssumeCapacity(.{
.src_offset = array_write_offset,
.dst_offset = array_offset + write_info.element_offset * element_size,
.size = array_write_size,
});
}
std.debug.assert(regions.items.len > 0);
}
var staging_buffer: StagingBuffer = try .init(engine, .{
.target_queue = self.target_queue,
.capacity = write_size,
});
defer staging_buffer.deinit(engine);
const staging_memory = try staging_buffer.map(engine);
if (write_info.header) |header| {
@memcpy(staging_memory[0..header_size], std.mem.asBytes(&header));
}
@memcpy(staging_memory[array_write_offset..write_size], std.mem.sliceAsBytes(write_info.elements));
staging_buffer.unmap(engine);
const command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
command_buffer.copyBuffer(
staging_buffer.buffer,
self.buffer,
@intCast(regions.items.len),
regions.items.ptr,
);
try command_buffer.endCommandBuffer();
const fence = try engine.createFence(.{});
defer engine.destroyFence(fence);
try engine.submitTransferCommandBuffer(command_buffer, fence);
try engine.waitForFence(fence);
}
pub fn writeRaw(self: Self, engine: *Engine, data_offset: u32, data: []const u8) !void {
const array_size = self.array_capacity * element_size;
const size = array_offset + array_size;
const data_size = std.math.cast(u32, data.len) orelse return error.Overflow;
std.debug.assert(data_offset + data_size <= size);
const regions = [_]vk.BufferCopy{
.{
.src_offset = 0,
.dst_offset = data_offset,
.size = data_size,
},
};
var staging_buffer = try StagingBuffer.init(engine, .{
.target_queue = self.target_queue,
.capacity = data_size,
});
defer staging_buffer.deinit(engine);
const staging_memory = try staging_buffer.map(engine);
@memcpy(staging_memory, data);
staging_buffer.unmap(engine);
const command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
command_buffer.copyBuffer(
staging_buffer.buffer,
self.buffer,
@intCast(regions.items.len),
regions.items.ptr,
);
try command_buffer.endCommandBuffer();
const fence = try engine.createFence(.{});
defer engine.destroyFence(fence);
try engine.submitTransferCommandBuffer(command_buffer, fence);
try engine.waitForFence(fence);
}
};
}

View File

@@ -1,85 +0,0 @@
const IndexBuffer = @This();
const std = @import("std");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig");
const QSM = @import("QueueSharingMode.zig");
buffer: vk.Buffer,
memory: vk.DeviceMemory,
index_count: usize,
pub fn init(engine: *Engine, index_count: usize) !IndexBuffer {
const size = std.math.mul(usize, index_count, @sizeOf(u16)) catch return error.OutOfMemory;
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
const buffer = try engine.device.createBuffer(&.{
.size = size,
.usage = .{
.transfer_dst_bit = true,
.index_buffer_bit = true,
},
.sharing_mode = qsm.sharing_mode,
.queue_family_index_count = qsm.queue_family_index_count,
.p_queue_family_indices = qsm.p_queue_family_indices,
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
try engine.device.bindBufferMemory(buffer, memory, 0);
return .{
.buffer = buffer,
.memory = memory,
.index_count = index_count,
};
}
pub fn deinit(self: *IndexBuffer, engine: *Engine) void {
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
self.* = undefined;
}
pub fn write(self: IndexBuffer, engine: *Engine, indices: []const u16) !void {
std.debug.assert(indices.len == self.index_count);
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
const size = std.mem.sliceAsBytes(indices).len;
var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(indices), engine.graphics_queue.allocation.family);
defer staging_buffer.deinit(engine);
const command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
const regions = [_]vk.BufferCopy{
.{
.src_offset = 0,
.dst_offset = 0,
.size = size,
},
};
command_buffer.copyBuffer(
staging_buffer.buffer,
self.buffer,
regions.len,
&regions,
);
try command_buffer.endCommandBuffer();
try engine.submitTransferCommandBuffer(command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
}

View File

@@ -4,51 +4,69 @@ const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const Engine = @import("Engine.zig"); const Engine = @import("Engine.zig");
const QSM = @import("QueueSharingMode.zig"); const TargetQueue = @import("TargetQueue.zig").TargetQueue;
buffer: vk.Buffer, buffer: vk.Buffer,
memory: vk.DeviceMemory, device_memory: vk.DeviceMemory,
capacity: u32,
pub fn init(engine: *Engine, data: []const u8, destination_queue_family: u32) !StagingBuffer { pub const InitInfo = struct {
target_queue: TargetQueue,
capacity: u32,
};
pub fn init(engine: *Engine, init_info: InitInfo) !StagingBuffer {
const target_queue_family = switch (init_info.target_queue) {
.graphics => engine.graphics_queue.allocation.family,
.compute => engine.compute_queue.allocation.family,
};
const transfer_queue_family = engine.transfer_queue.allocation.family; const transfer_queue_family = engine.transfer_queue.allocation.family;
const qsm = QSM.resolve(destination_queue_family, transfer_queue_family); const buffer = try engine.createBuffer(.{
const buffer = try engine.device.createBuffer(&.{ .size = init_info.capacity,
.size = data.len,
.usage = .{ .usage = .{
.transfer_src_bit = true, .transfer_src_bit = true,
}, },
.sharing_mode = qsm.sharing_mode, .queue_family_indices = &.{ target_queue_family, transfer_queue_family },
.queue_family_index_count = qsm.queue_family_index_count, });
.p_queue_family_indices = qsm.p_queue_family_indices, errdefer engine.destroyBuffer(buffer);
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
const memory = try engine.allocate( const device_memory = try engine.allocate(
memory_requirements, memory_requirements,
.{ .{
.host_visible_bit = true, .host_visible_bit = true,
.host_coherent_bit = true, .host_coherent_bit = true,
}, },
); );
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); errdefer engine.freeMemory(device_memory);
try engine.device.bindBufferMemory(buffer, memory, 0); try engine.device.bindBufferMemory(buffer, device_memory, 0);
const mapped_memory: [*]u8 = @ptrCast(try engine.device.mapMemory(memory, 0, data.len, .{}) orelse return error.OutOfMemory);
defer engine.device.unmapMemory(memory);
@memcpy(mapped_memory, data);
return .{ return .{
.buffer = buffer, .buffer = buffer,
.memory = memory, .device_memory = device_memory,
.capacity = init_info.capacity,
}; };
} }
pub fn deinit(self: *StagingBuffer, engine: *Engine) void { pub fn deinit(self: *StagingBuffer, engine: *Engine) void {
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); engine.freeMemory(self.device_memory);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface); engine.destroyBuffer(self.buffer);
self.* = undefined; self.* = undefined;
} }
pub fn map(self: StagingBuffer, engine: *Engine) ![]u8 {
const mapped_memory = try self.mapPartial(engine, 0, self.capacity);
return mapped_memory;
}
pub fn mapPartial(self: StagingBuffer, engine: *Engine, offset: u32, len: u32) ![]u8 {
const mapped_memory = try engine.device.mapMemory(self.device_memory, offset, len, .{});
return @as([*]u8, @ptrCast(mapped_memory))[0..len];
}
pub fn unmap(self: StagingBuffer, engine: *Engine) void {
engine.device.unmapMemory(self.device_memory);
}

View File

@@ -1,200 +0,0 @@
const StorageBuffer = @This();
const std = @import("std");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig");
const QSM = @import("QueueSharingMode.zig");
buffer: vk.Buffer,
memory: vk.DeviceMemory,
prefix_size: usize,
element_size: usize,
array_offset: usize,
array_capacity: usize,
pub fn init(engine: *Engine, comptime PrefixType: type, comptime ElementType: type, array_capacity: usize) !StorageBuffer {
const prefix_size = @sizeOf(PrefixType);
const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType));
const element_size = @sizeOf(ElementType);
const array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory;
const size = std.math.add(usize, array_offset, array_capacity_in_bytes) catch return error.OutOfMemory;
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
const buffer = try engine.device.createBuffer(&.{
.size = size,
.usage = .{
.transfer_src_bit = true,
.transfer_dst_bit = true,
.storage_buffer_bit = true,
},
.sharing_mode = qsm.sharing_mode,
.queue_family_index_count = qsm.queue_family_index_count,
.p_queue_family_indices = qsm.p_queue_family_indices,
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
try engine.device.bindBufferMemory(buffer, memory, 0);
return .{
.buffer = buffer,
.memory = memory,
.prefix_size = prefix_size,
.element_size = element_size,
.array_offset = array_offset,
.array_capacity = array_capacity,
};
}
pub fn deinit(self: *StorageBuffer, engine: *Engine) void {
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
self.* = undefined;
}
pub fn enlarge(self: *StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, array_capacity: usize) !void {
std.debug.assert(array_capacity > self.array_capacity);
std.debug.assert(@sizeOf(PrefixType) == self.prefix_size);
std.debug.assert(@sizeOf(ElementType) == self.element_size);
std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType)));
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
const prefix_size = @sizeOf(PrefixType);
const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType));
const element_size = @sizeOf(ElementType);
const old_array_size_in_bytes = self.array_capacity * @sizeOf(ElementType);
const old_size = self.array_offset + old_array_size_in_bytes;
const new_array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory;
const new_size = std.math.add(usize, array_offset, new_array_capacity_in_bytes) catch return error.OutOfMemory;
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
const buffer = try engine.device.createBuffer(&.{
.size = new_size,
.usage = .{
.transfer_src_bit = true,
.transfer_dst_bit = true,
.storage_buffer_bit = true,
},
.sharing_mode = qsm.sharing_mode,
.queue_family_index_count = qsm.queue_family_index_count,
.p_queue_family_indices = qsm.p_queue_family_indices,
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
try engine.device.bindBufferMemory(buffer, memory, 0);
const command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
const regions = [_]vk.BufferCopy{
.{
.src_offset = 0,
.dst_offset = 0,
.size = old_size,
},
};
command_buffer.copyBuffer(
self.buffer,
buffer,
regions.len,
&regions,
);
try command_buffer.endCommandBuffer();
try engine.submitTransferCommandBuffer(command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
self.buffer = buffer;
self.memory = memory;
self.array_capacity = array_capacity;
}
pub fn write(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, elements: []const ElementType) !void {
try self.writeOffset(PrefixType, ElementType, engine, prefix, 0, elements);
}
pub fn writeOffset(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, offset: usize, elements: []const ElementType) !void {
std.debug.assert(offset + elements.len <= self.array_capacity);
std.debug.assert(@sizeOf(PrefixType) == self.prefix_size);
std.debug.assert(@sizeOf(ElementType) == self.element_size);
std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType)));
const array_size_in_bytes = elements.len * @sizeOf(ElementType);
const size = self.array_offset + array_size_in_bytes;
var regions_buffer: [2]vk.BufferCopy = undefined;
var regions: std.ArrayList(vk.BufferCopy) = .initBuffer(&regions_buffer);
if (self.prefix_size > 0) {
regions.appendAssumeCapacity(.{
.src_offset = 0,
.dst_offset = 0,
.size = self.prefix_size,
});
}
if (array_size_in_bytes > 0) {
regions.appendAssumeCapacity(.{
.src_offset = self.array_offset,
.dst_offset = self.array_offset + offset * @sizeOf(ElementType),
.size = array_size_in_bytes,
});
}
if (regions.items.len == 0) {
std.log.warn("Zero-length StorageBuffer({s}, {s}) write", .{ @typeName(PrefixType), @typeName(ElementType) });
return;
}
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
const data = try engine.vk_allocator.allocator.alloc(u8, size);
defer engine.vk_allocator.allocator.free(data);
@memcpy(data[0..@sizeOf(PrefixType)], std.mem.asBytes(&prefix));
@memcpy(data[self.array_offset..], std.mem.sliceAsBytes(elements));
var staging_buffer: StagingBuffer = try .init(engine, data, engine.graphics_queue.allocation.family);
defer staging_buffer.deinit(engine);
const command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
command_buffer.copyBuffer(
staging_buffer.buffer,
self.buffer,
@intCast(regions.items.len),
regions.items.ptr,
);
try command_buffer.endCommandBuffer();
try engine.submitTransferCommandBuffer(command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
}

View File

@@ -6,11 +6,19 @@ const vk = @import("vulkan");
const Engine = @import("Engine.zig"); const Engine = @import("Engine.zig");
const QSM = @import("QueueSharingMode.zig"); const QSM = @import("QueueSharingMode.zig");
engine: *Engine,
params: Params, params: Params,
render_pass: vk.RenderPass, render_pass: vk.RenderPass,
extent: vk.Extent2D = .{ .width = 0, .height = 0 },
swapchain: vk.SwapchainKHR = .null_handle, swapchain: vk.SwapchainKHR = .null_handle,
swapchain_images: []SwapchainImage = &.{}, swapchain_images: []SwapchainImage = &.{},
image_index: u32 = 0,
semaphore_image_acquired: vk.Semaphore = .null_handle,
pub const PresentResult = enum {
optimal,
suboptimal,
};
pub fn init(engine: *Engine) !Swapchain { pub fn init(engine: *Engine) !Swapchain {
const params: Params = try .init(engine); const params: Params = try .init(engine);
@@ -20,7 +28,7 @@ pub fn init(engine: *Engine) !Swapchain {
.{ .{
.format = params.surface_format.format, .format = params.surface_format.format,
.samples = .{ .@"1_bit" = true }, .samples = .{ .@"1_bit" = true },
.load_op = .dont_care, .load_op = .clear,
.store_op = .store, .store_op = .store,
.stencil_load_op = .dont_care, .stencil_load_op = .dont_care,
.stencil_store_op = .dont_care, .stencil_store_op = .dont_care,
@@ -54,47 +62,49 @@ pub fn init(engine: *Engine) !Swapchain {
errdefer engine.device.destroyRenderPass(render_pass, &engine.vk_allocator.interface); errdefer engine.device.destroyRenderPass(render_pass, &engine.vk_allocator.interface);
var swapchain: Swapchain = .{ var swapchain: Swapchain = .{
.engine = engine,
.params = params, .params = params,
.render_pass = render_pass, .render_pass = render_pass,
}; };
try recreate(&swapchain); try recreate(&swapchain, engine);
return swapchain; return swapchain;
} }
pub fn deinit(self: *Swapchain) void { pub fn deinit(self: *Swapchain, engine: *Engine) void {
const allocator = self.engine.vk_allocator.allocator; const allocator = engine.vk_allocator.allocator;
for (self.swapchain_images) |swapchain_image| { for (self.swapchain_images) |swapchain_image| {
swapchain_image.deinit(self.engine); swapchain_image.deinit(engine);
} }
allocator.free(self.swapchain_images); allocator.free(self.swapchain_images);
if (self.swapchain != .null_handle) { if (self.swapchain != .null_handle) {
self.engine.device.destroySwapchainKHR(self.swapchain, &self.engine.vk_allocator.interface); engine.device.destroySwapchainKHR(self.swapchain, &engine.vk_allocator.interface);
} }
self.engine.device.destroyRenderPass(self.render_pass, &self.engine.vk_allocator.interface); engine.device.destroyRenderPass(self.render_pass, &engine.vk_allocator.interface);
self.* = undefined; self.* = undefined;
} }
pub fn recreate(self: *Swapchain) !void { pub fn recreate(self: *Swapchain, engine: *Engine) !void {
const mode = &self.engine.mode.surface; const mode = &engine.mode.surface;
const allocator = self.engine.vk_allocator.allocator; const allocator = engine.vk_allocator.allocator;
const old_swapchain = self.swapchain; const old_swapchain = self.swapchain;
const old_swapchain_images = self.swapchain_images; const old_swapchain_images = self.swapchain_images;
const old_semaphore_image_acquired = self.semaphore_image_acquired;
const extent = try getCurrentExtent(self.engine); const extent = try getCurrentExtent(engine);
std.log.debug("Recreating swapchain with extent of {d}×{d}...", .{ extent.width, extent.height });
// --- CREATE NEW SWAPCHAIN ------------------------------------------------ // --- CREATE NEW SWAPCHAIN ------------------------------------------------
const surface_capabilities = try self.engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.engine.physical_device, mode.surface); const surface_capabilities = try engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(engine.physical_device, mode.surface);
const qsm = QSM.resolve(self.engine.graphics_queue.allocation.family, mode.presentation_queue.allocation.family); const qsm = QSM.resolve(engine.graphics_queue.allocation.family, mode.presentation_queue.allocation.family);
const new_swapchain = try self.engine.device.createSwapchainKHR(&.{ const new_swapchain = try engine.device.createSwapchainKHR(&.{
.surface = mode.surface, .surface = mode.surface,
.min_image_count = self.params.image_count, .min_image_count = self.params.image_count,
.image_format = self.params.surface_format.format, .image_format = self.params.surface_format.format,
@@ -110,8 +120,8 @@ pub fn recreate(self: *Swapchain) !void {
.present_mode = .fifo_khr, .present_mode = .fifo_khr,
.clipped = .true, .clipped = .true,
.old_swapchain = old_swapchain, .old_swapchain = old_swapchain,
}, &self.engine.vk_allocator.interface); }, &engine.vk_allocator.interface);
errdefer self.engine.device.destroySwapchainKHR(new_swapchain, &self.engine.vk_allocator.interface); errdefer engine.device.destroySwapchainKHR(new_swapchain, &engine.vk_allocator.interface);
// --- DESTROY OLD SWAPCHAIN AND ITS IMAGES -------------------------------- // --- DESTROY OLD SWAPCHAIN AND ITS IMAGES --------------------------------
@@ -120,29 +130,36 @@ pub fn recreate(self: *Swapchain) !void {
// null and deinit the old swapchain and images. // null and deinit the old swapchain and images.
for (old_swapchain_images) |swapchain_image| { for (old_swapchain_images) |swapchain_image| {
swapchain_image.deinit(self.engine); swapchain_image.deinit(engine);
} }
allocator.free(self.swapchain_images); allocator.free(self.swapchain_images);
self.swapchain_images = &.{}; self.swapchain_images = &.{};
if (old_swapchain != .null_handle) { if (old_swapchain != .null_handle) {
self.engine.device.destroySwapchainKHR(old_swapchain, &self.engine.vk_allocator.interface); engine.device.destroySwapchainKHR(old_swapchain, &engine.vk_allocator.interface);
self.swapchain = .null_handle; self.swapchain = .null_handle;
} }
self.extent = .{ .width = 0, .height = 0 };
if (old_semaphore_image_acquired != .null_handle) {
engine.destroySemaphore(old_semaphore_image_acquired);
self.semaphore_image_acquired = .null_handle;
}
// --- CREATE NEW SWAPCHAIN IMAGES ----------------------------------------- // --- CREATE NEW SWAPCHAIN IMAGES -----------------------------------------
const new_swapchain_images = blk: { const new_swapchain_images = blk: {
const images = try self.engine.device.getSwapchainImagesAllocKHR(new_swapchain, allocator); const images = try engine.device.getSwapchainImagesAllocKHR(new_swapchain, allocator);
defer allocator.free(images); defer allocator.free(images);
var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len); var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len);
errdefer swapchain_images.deinit(allocator); errdefer swapchain_images.deinit(allocator);
errdefer for (swapchain_images.items) |x| x.deinit(self.engine); errdefer for (swapchain_images.items) |x| x.deinit(engine);
for (images) |image| { for (images) |image| {
swapchain_images.appendAssumeCapacity(try SwapchainImage.init(self.engine, .{ swapchain_images.appendAssumeCapacity(try SwapchainImage.init(engine, .{
.image = image, .image = image,
.format = self.params.surface_format.format, .format = self.params.surface_format.format,
.render_pass = self.render_pass, .render_pass = self.render_pass,
@@ -154,20 +171,105 @@ pub fn recreate(self: *Swapchain) !void {
}; };
errdefer { errdefer {
for (new_swapchain_images) |swapchain_image| { for (new_swapchain_images) |swapchain_image| {
swapchain_image.deinit(self.engine); swapchain_image.deinit(engine);
} }
allocator.free(new_swapchain_images); allocator.free(new_swapchain_images);
} }
// --- ACQUIRE NEXT IMAGE --------------------------------------------------
var semaphore_image_acquired = try engine.createSemaphore();
errdefer engine.destroySemaphore(semaphore_image_acquired);
const res = try engine.device.acquireNextImageKHR(new_swapchain, std.math.maxInt(u64), semaphore_image_acquired, .null_handle);
if (res.result == .not_ready or res.result == .timeout) {
return error.ImageAcquireFailed;
}
std.mem.swap(vk.Semaphore, &new_swapchain_images[res.image_index].semaphore_image_acquired, &semaphore_image_acquired);
// --- COMMIT -------------------------------------------------------------- // --- COMMIT --------------------------------------------------------------
self.extent = extent;
self.swapchain = new_swapchain; self.swapchain = new_swapchain;
self.swapchain_images = new_swapchain_images; self.swapchain_images = new_swapchain_images;
self.image_index = res.image_index;
self.semaphore_image_acquired = semaphore_image_acquired;
std.log.debug("Swapchain recreated.", .{});
} }
fn getCurrentExtent(self: *Engine) !vk.Extent2D { pub fn present(self: *Swapchain, engine: *Engine, command_buffer: vk.CommandBuffer) !PresentResult {
const mode = &self.mode.surface; const device = engine.device;
const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, mode.surface); const mode = &engine.mode.surface;
std.log.debug("Presenting command buffer {X}.", .{@intFromEnum(command_buffer)});
// --- WAIT FOR CURRENT FRAME TO FINISH ------------------------------------
const current_swapchain_image = &self.swapchain_images[self.image_index];
try engine.waitForFence(current_swapchain_image.fence);
if (current_swapchain_image.command_buffer != .null_handle) {
device.freeCommandBuffers(engine.graphics_command_pool, 1, @ptrCast(&current_swapchain_image.command_buffer));
current_swapchain_image.command_buffer = .null_handle;
}
try engine.resetFence(current_swapchain_image.fence);
// --- SUBMIT COMMAND BUFFER -----------------------------------------------
const wait_semaphores = [_]vk.Semaphore{
current_swapchain_image.semaphore_image_acquired,
};
const pipeline_stages_flags = [_]vk.PipelineStageFlags{
.{ .top_of_pipe_bit = true },
};
std.debug.assert(wait_semaphores.len == pipeline_stages_flags.len);
const signal_semaphores = [_]vk.Semaphore{
current_swapchain_image.semaphore_render_finished,
};
current_swapchain_image.command_buffer = command_buffer;
try device.queueSubmit(engine.graphics_queue.handle, 1, &.{
.{
.wait_semaphore_count = wait_semaphores.len,
.p_wait_semaphores = &wait_semaphores,
.p_wait_dst_stage_mask = &pipeline_stages_flags,
.command_buffer_count = 1,
.p_command_buffers = @ptrCast(&command_buffer),
.signal_semaphore_count = signal_semaphores.len,
.p_signal_semaphores = &signal_semaphores,
},
}, current_swapchain_image.fence);
// --- PRESENT CURRENT FRAME -----------------------------------------------
_ = try device.queuePresentKHR(mode.presentation_queue.handle, &.{
.wait_semaphore_count = 1,
.p_wait_semaphores = @ptrCast(&current_swapchain_image.semaphore_render_finished),
.swapchain_count = 1,
.p_swapchains = @ptrCast(&self.swapchain),
.p_image_indices = @ptrCast(&self.image_index),
});
// --- ACQUIRE NEXT FRAME --------------------------------------------------
const res = try device.acquireNextImageKHR(self.swapchain, std.math.maxInt(u64), self.semaphore_image_acquired, .null_handle);
std.mem.swap(vk.Semaphore, &self.swapchain_images[res.image_index].semaphore_image_acquired, &self.semaphore_image_acquired);
self.image_index = res.image_index;
return switch (res.result) {
.success => .optimal,
.suboptimal_khr => .suboptimal,
else => unreachable,
};
}
fn getCurrentExtent(engine: *Engine) !vk.Extent2D {
const mode = &engine.mode.surface;
const surface_capabilities = try engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(engine.physical_device, mode.surface);
if (surface_capabilities.current_extent.width != std.math.maxInt(u32) and surface_capabilities.current_extent.height != std.math.maxInt(u32)) { if (surface_capabilities.current_extent.width != std.math.maxInt(u32) and surface_capabilities.current_extent.height != std.math.maxInt(u32)) {
return surface_capabilities.current_extent; return surface_capabilities.current_extent;
@@ -223,6 +325,8 @@ const Params = struct {
if (surface_capabilities.max_image_count > 0) surface_capabilities.max_image_count else std.math.maxInt(u32), if (surface_capabilities.max_image_count > 0) surface_capabilities.max_image_count else std.math.maxInt(u32),
); );
std.log.debug("Using surface format \"{s}\" and color space \"{s}\" with {d} images.", .{ @tagName(surface_format.format), @tagName(surface_format.color_space), image_count });
return .{ return .{
.surface_format = surface_format, .surface_format = surface_format,
.image_count = image_count, .image_count = image_count,
@@ -237,6 +341,7 @@ const SwapchainImage = struct {
semaphore_render_finished: vk.Semaphore, semaphore_render_finished: vk.Semaphore,
fence: vk.Fence, fence: vk.Fence,
framebuffer: vk.Framebuffer, framebuffer: vk.Framebuffer,
command_buffer: vk.CommandBuffer,
const InitProps = struct { const InitProps = struct {
image: vk.Image, image: vk.Image,
@@ -246,16 +351,10 @@ const SwapchainImage = struct {
}; };
fn init(engine: *Engine, props: InitProps) !SwapchainImage { fn init(engine: *Engine, props: InitProps) !SwapchainImage {
const image_view = try engine.device.createImageView(&.{ const image_view = try engine.createImageView(.{
.image = props.image, .image = props.image,
.view_type = .@"2d", .view_type = .@"2d",
.format = props.format, .format = props.format,
.components = .{
.r = .identity,
.g = .identity,
.b = .identity,
.a = .identity,
},
.subresource_range = .{ .subresource_range = .{
.aspect_mask = .{ .color_bit = true }, .aspect_mask = .{ .color_bit = true },
.base_mip_level = 0, .base_mip_level = 0,
@@ -263,31 +362,28 @@ const SwapchainImage = struct {
.base_array_layer = 0, .base_array_layer = 0,
.layer_count = 1, .layer_count = 1,
}, },
}, &engine.vk_allocator.interface); });
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface); errdefer engine.destroyImageView(image_view);
const semaphore_image_acquired = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface); const semaphore_image_acquired = try engine.createSemaphore();
errdefer engine.device.destroySemaphore(semaphore_image_acquired, &engine.vk_allocator.interface); errdefer engine.destroySemaphore(semaphore_image_acquired);
const semaphore_render_finished = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface); const semaphore_render_finished = try engine.createSemaphore();
errdefer engine.device.destroySemaphore(semaphore_render_finished, &engine.vk_allocator.interface); errdefer engine.destroySemaphore(semaphore_render_finished);
const fence = try engine.device.createFence(&.{ const fence = try engine.createFence(.{
.flags = .{ .signaled_bit = true }, .flags = .{ .signaled_bit = true },
}, &engine.vk_allocator.interface); });
errdefer engine.device.destroyFence(fence, &engine.vk_allocator.interface); errdefer engine.destroyFence(fence);
const attachments = [_]vk.ImageView{image_view}; const framebuffer = try engine.createFramebuffer(.{
const framebuffer = try engine.device.createFramebuffer(&.{
.render_pass = props.render_pass, .render_pass = props.render_pass,
.attachment_count = attachments.len, .attachments = &.{image_view},
.p_attachments = &attachments,
.width = props.extent.width, .width = props.extent.width,
.height = props.extent.height, .height = props.extent.height,
.layers = 1, .layers = 1,
}, &engine.vk_allocator.interface); });
errdefer engine.device.destroyFramebuffer(framebuffer, &engine.vk_allocator.interface); errdefer engine.destroyFramebuffer(framebuffer);
return .{ return .{
.image = props.image, .image = props.image,
@@ -296,20 +392,16 @@ const SwapchainImage = struct {
.semaphore_render_finished = semaphore_render_finished, .semaphore_render_finished = semaphore_render_finished,
.fence = fence, .fence = fence,
.framebuffer = framebuffer, .framebuffer = framebuffer,
.command_buffer = .null_handle,
}; };
} }
fn deinit(self: SwapchainImage, engine: *Engine) void { fn deinit(self: SwapchainImage, engine: *Engine) void {
self.waitForFence(engine) catch return; engine.waitForFence(self.fence) catch {};
engine.device.destroyFramebuffer(self.framebuffer, &engine.vk_allocator.interface); engine.destroyFramebuffer(self.framebuffer);
engine.device.destroyFence(self.fence, &engine.vk_allocator.interface); engine.destroyFence(self.fence);
engine.device.destroySemaphore(self.semaphore_render_finished, &engine.vk_allocator.interface); engine.destroySemaphore(self.semaphore_render_finished);
engine.device.destroySemaphore(self.semaphore_image_acquired, &engine.vk_allocator.interface); engine.destroySemaphore(self.semaphore_image_acquired);
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface); engine.destroyImageView(self.image_view);
}
fn waitForFence(self: SwapchainImage, engine: *Engine) !void {
const fences = [_]vk.Fence{self.fence};
_ = try engine.device.waitForFences(fences.len, &fences, .true, std.math.maxInt(u64));
} }
}; };

View File

@@ -0,0 +1,4 @@
pub const TargetQueue = enum {
graphics,
compute,
};

View File

@@ -5,7 +5,7 @@ const vk = @import("vulkan");
const Engine = @import("Engine.zig"); const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig"); const StagingBuffer = @import("StagingBuffer.zig");
const QSM = @import("QueueSharingMode.zig"); const TargetQueue = @import("TargetQueue.zig").TargetQueue;
pub const Usage = enum { pub const Usage = enum {
base_color, base_color,
@@ -13,7 +13,7 @@ pub const Usage = enum {
occlusion_roughness_metallic, occlusion_roughness_metallic,
emissive, emissive,
pub fn format(self: Usage) vk.Format { pub fn vkFormat(self: Usage) vk.Format {
return switch (self) { return switch (self) {
.base_color => .r8g8b8a8_srgb, .base_color => .r8g8b8a8_srgb,
.normal => .r8g8b8a8_snorm, .normal => .r8g8b8a8_snorm,
@@ -22,7 +22,7 @@ pub const Usage = enum {
}; };
} }
pub fn sampleCount(self: Usage) u32 { pub fn samplesPerTexel(self: Usage) u32 {
return switch (self) { return switch (self) {
.base_color => 4, .base_color => 4,
.normal => 4, .normal => 4,
@@ -34,47 +34,58 @@ pub const Usage = enum {
pub fn SampleType(comptime self: Usage) type { pub fn SampleType(comptime self: Usage) type {
return switch (self) { return switch (self) {
.base_color => u8, .base_color => u8,
.normal => u8, .normal => i8,
.occlusion_roughness_metallic => u8, .occlusion_roughness_metallic => u8,
.emissive => f16, .emissive => f16,
}; };
} }
pub fn sampleSize(self: Usage) u32 { pub fn bytesPerSample(self: Usage) u32 {
return switch (self) { return switch (self) {
inline else => |x| @sizeOf(SampleType(x)), inline else => |x| @sizeOf(SampleType(x)),
}; };
} }
pub fn TexelType(comptime self: Usage) type { pub fn TexelType(comptime self: Usage) type {
return [self.sampleCount()]SampleType(self); return [self.samplesPerTexel()]SampleType(self);
} }
pub fn texelSize(self: Usage) u32 { pub fn bytesPerTexel(self: Usage) u32 {
return switch (self) { return switch (self) {
inline else => |x| @sizeOf(TexelType(x)), inline else => |x| @sizeOf(TexelType(x)),
}; };
} }
}; };
pub const InitInfo = struct {
width: u32,
height: u32,
usage: Usage,
target_queue: TargetQueue,
};
image: vk.Image, image: vk.Image,
image_view: vk.ImageView, image_view: vk.ImageView,
memory: vk.DeviceMemory, device_memory: vk.DeviceMemory,
target_queue: TargetQueue,
width: u32, width: u32,
height: u32, height: u32,
usage: Usage, usage: Usage,
pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture { pub fn init(engine: *Engine, init_info: InitInfo) !Texture {
const format: vk.Format = usage.format(); const target_queue_family = switch (init_info.target_queue) {
.graphics => engine.graphics_queue.allocation.family,
.compute => engine.compute_queue.allocation.family,
};
const transfer_queue_family = engine.transfer_queue.allocation.family;
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family); const image = try engine.createImage(.{
const image = try engine.device.createImage(&.{
.image_type = .@"2d", .image_type = .@"2d",
.format = format, .format = init_info.usage.vkFormat(),
.extent = .{ .extent = .{
.width = width, .width = init_info.width,
.height = height, .height = init_info.height,
.depth = 1, .depth = 1,
}, },
.mip_levels = 1, .mip_levels = 1,
@@ -85,29 +96,21 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture {
.transfer_dst_bit = true, .transfer_dst_bit = true,
.sampled_bit = true, .sampled_bit = true,
}, },
.sharing_mode = qsm.sharing_mode, .queue_family_indices = &.{ target_queue_family, transfer_queue_family },
.queue_family_index_count = qsm.queue_family_index_count,
.p_queue_family_indices = qsm.p_queue_family_indices,
.initial_layout = .undefined, .initial_layout = .undefined,
}, &engine.vk_allocator.interface); });
errdefer engine.device.destroyImage(image, &engine.vk_allocator.interface); errdefer engine.destroyImage(image);
const memory_requirements = engine.device.getImageMemoryRequirements(image); const memory_requirements = engine.device.getImageMemoryRequirements(image);
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); const device_memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); errdefer engine.freeMemory(device_memory);
try engine.device.bindImageMemory(image, memory, 0); try engine.device.bindImageMemory(image, device_memory, 0);
const image_view = try engine.device.createImageView(&.{ const image_view = try engine.createImageView(.{
.image = image, .image = image,
.view_type = .@"2d", .view_type = .@"2d",
.format = format, .format = init_info.usage.vkFormat(),
.components = .{
.r = .identity,
.g = .identity,
.b = .identity,
.a = .identity,
},
.subresource_range = .{ .subresource_range = .{
.aspect_mask = .{ .color_bit = true }, .aspect_mask = .{ .color_bit = true },
.base_mip_level = 0, .base_mip_level = 0,
@@ -115,49 +118,72 @@ pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture {
.base_array_layer = 0, .base_array_layer = 0,
.layer_count = 1, .layer_count = 1,
}, },
}, &engine.vk_allocator.interface); });
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface); errdefer engine.destroyImageView(image_view);
return .{ return .{
.image = image, .image = image,
.image_view = image_view, .image_view = image_view,
.memory = memory, .device_memory = device_memory,
.width = width, .target_queue = init_info.target_queue,
.height = height, .width = init_info.width,
.usage = usage, .height = init_info.height,
.usage = init_info.usage,
}; };
} }
pub fn deinit(self: *Texture, engine: *Engine) void { pub fn deinit(self: *Texture, engine: *Engine) void {
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface); engine.destroyImageView(self.image_view);
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); engine.freeMemory(self.device_memory);
engine.device.destroyImage(self.image, &engine.vk_allocator.interface); engine.destroyImage(self.image);
self.* = undefined; self.* = undefined;
} }
pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T) !void { pub fn writeTexels(self: Texture, comptime TexelType: type, engine: *Engine, texels: []const TexelType) !void {
const bytes_per_texel = self.usage.texelSize(); const texel_count = try std.math.mul(u32, self.width, self.height);
const bytes_per_row = self.width * bytes_per_texel; std.debug.assert(texels.len == texel_count);
const byte_length = @as(usize, self.height) * bytes_per_row; switch (self.usage) {
inline else => |x| std.debug.assert(TexelType == x.TexelType()),
}
std.debug.assert(data.len * @sizeOf(T) == byte_length); try self.writeRaw(engine, std.mem.sliceAsBytes(texels));
}
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface); pub fn writeSamples(self: Texture, comptime SampleType: type, engine: *Engine, samples: []const SampleType) !void {
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface); const texel_count = try std.math.mul(u32, self.width, self.height);
const sample_count = try std.math.mul(u32, texel_count, self.usage.samplesPerTexel());
std.debug.assert(samples.len == sample_count);
switch (self.usage) {
inline else => |x| std.debug.assert(SampleType == x.SampleType()),
}
var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(data), engine.graphics_queue.allocation.family); try self.writeRaw(engine, std.mem.sliceAsBytes(samples));
}
pub fn writeRaw(self: Texture, engine: *Engine, data: []const u8) !void {
const texel_count = try std.math.mul(u32, self.width, self.height);
const byte_length = try std.math.mul(u32, texel_count, self.usage.bytesPerTexel());
std.debug.assert(data.len == byte_length);
var staging_buffer = try StagingBuffer.init(engine, .{
.capacity = @intCast(byte_length),
.target_queue = self.target_queue,
});
defer staging_buffer.deinit(engine); defer staging_buffer.deinit(engine);
const staging_memory = try staging_buffer.map(engine);
@memcpy(staging_memory, data);
staging_buffer.unmap(engine);
// --- TRANSITION TO TRANSFER_DST_OPTIMAL AND COPY -----------------
const transfer_command_buffer = try engine.allocateTransferCommandBuffer(); const transfer_command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(transfer_command_buffer); defer engine.freeTransferCommandBuffer(transfer_command_buffer);
const graphics_command_buffer = try engine.allocateGraphicsCommandBuffer();
defer engine.freeGraphicsCommandBuffer(graphics_command_buffer);
try transfer_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); try transfer_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
const pre_copy_barriers = [_]vk.ImageMemoryBarrier{ const transfer_transition_barriers = [_]vk.ImageMemoryBarrier{
.{ .{
.src_access_mask = .{}, .src_access_mask = .{},
.dst_access_mask = .{ .transfer_write_bit = true }, .dst_access_mask = .{ .transfer_write_bit = true },
@@ -184,8 +210,8 @@ pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T)
null, null,
0, 0,
null, null,
pre_copy_barriers.len, transfer_transition_barriers.len,
&pre_copy_barriers, &transfer_transition_barriers,
); );
const regions = [_]vk.BufferImageCopy{ const regions = [_]vk.BufferImageCopy{
@@ -213,14 +239,28 @@ pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T)
); );
try transfer_command_buffer.endCommandBuffer(); try transfer_command_buffer.endCommandBuffer();
try engine.submitTransferCommandBuffer(transfer_command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); const semaphore = try engine.createSemaphore();
try engine.device.resetFences(1, @ptrCast(&fence)); defer engine.destroySemaphore(semaphore);
const transfer_submits = [_]vk.SubmitInfo{
.{
.command_buffer_count = 1,
.p_command_buffers = @ptrCast(&transfer_command_buffer.handle),
.signal_semaphore_count = 1,
.p_signal_semaphores = @ptrCast(&semaphore),
},
};
try engine.device.queueSubmit(engine.transfer_queue.handle, transfer_submits.len, &transfer_submits, .null_handle);
// --- TRANSITION TO SHADER_READ_ONLY_OPTIMAL ----------------------
const graphics_command_buffer = try engine.allocateGraphicsCommandBuffer();
defer engine.freeGraphicsCommandBuffer(graphics_command_buffer);
try graphics_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); try graphics_command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
const post_copy_barriers = [_]vk.ImageMemoryBarrier{ const graphics_transition_barriers = [_]vk.ImageMemoryBarrier{
.{ .{
.src_access_mask = .{ .transfer_write_bit = true }, .src_access_mask = .{ .transfer_write_bit = true },
.dst_access_mask = .{ .shader_read_bit = true }, .dst_access_mask = .{ .shader_read_bit = true },
@@ -247,12 +287,29 @@ pub fn write(self: Texture, comptime T: type, engine: *Engine, data: []const T)
null, null,
0, 0,
null, null,
post_copy_barriers.len, graphics_transition_barriers.len,
&post_copy_barriers, &graphics_transition_barriers,
); );
try graphics_command_buffer.endCommandBuffer(); try graphics_command_buffer.endCommandBuffer();
try engine.submitGraphicsCommandBuffer(graphics_command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64)); const wait_stage_masks = [_]vk.PipelineStageFlags{
.{ .top_of_pipe_bit = true },
};
const graphics_submits = [_]vk.SubmitInfo{
.{
.command_buffer_count = 1,
.p_command_buffers = @ptrCast(&graphics_command_buffer.handle),
.wait_semaphore_count = 1,
.p_wait_semaphores = @ptrCast(&semaphore),
.p_wait_dst_stage_mask = &wait_stage_masks,
},
};
const fence = try engine.createFence(.{});
defer engine.destroyFence(fence);
try engine.device.queueSubmit(engine.graphics_queue.handle, graphics_submits.len, &graphics_submits, fence);
try engine.waitForFence(fence);
} }

View File

@@ -1,89 +0,0 @@
const VertexBuffer = @This();
const std = @import("std");
const vk = @import("vulkan");
const Engine = @import("Engine.zig");
const StagingBuffer = @import("StagingBuffer.zig");
const QSM = @import("QueueSharingMode.zig");
buffer: vk.Buffer,
memory: vk.DeviceMemory,
vertex_size: usize,
vertex_count: usize,
pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !VertexBuffer {
const vertex_size = @sizeOf(VertexType);
const size = std.math.mul(usize, vertex_count, vertex_size) catch return error.OutOfMemory;
const qsm = QSM.resolve(engine.graphics_queue.allocation.family, engine.transfer_queue.allocation.family);
const buffer = try engine.device.createBuffer(&.{
.size = size,
.usage = .{
.transfer_dst_bit = true,
.vertex_buffer_bit = true,
},
.sharing_mode = qsm.sharing_mode,
.queue_family_index_count = qsm.queue_family_index_count,
.p_queue_family_indices = qsm.p_queue_family_indices,
}, &engine.vk_allocator.interface);
errdefer engine.device.destroyBuffer(buffer, &engine.vk_allocator.interface);
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
try engine.device.bindBufferMemory(buffer, memory, 0);
return .{
.buffer = buffer,
.memory = memory,
.vertex_size = vertex_size,
.vertex_count = vertex_count,
};
}
pub fn deinit(self: *VertexBuffer, engine: *Engine) void {
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
engine.device.destroyBuffer(self.buffer, &engine.vk_allocator.interface);
self.* = undefined;
}
pub fn write(self: VertexBuffer, comptime VertexType: type, engine: *Engine, vertices: []const VertexType) !void {
std.debug.assert(vertices.len == self.vertex_count);
std.debug.assert(@sizeOf(VertexType) == self.vertex_size);
const fence = try engine.device.createFence(&.{}, &engine.vk_allocator.interface);
defer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
const size = std.mem.sliceAsBytes(vertices).len;
var staging_buffer: StagingBuffer = try .init(engine, std.mem.sliceAsBytes(vertices), engine.graphics_queue.allocation.family);
defer staging_buffer.deinit(engine);
const command_buffer = try engine.allocateTransferCommandBuffer();
defer engine.freeTransferCommandBuffer(command_buffer);
try command_buffer.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
const regions = [_]vk.BufferCopy{
.{
.src_offset = 0,
.dst_offset = 0,
.size = size,
},
};
command_buffer.copyBuffer(
staging_buffer.buffer,
self.buffer,
regions.len,
&regions,
);
try command_buffer.endCommandBuffer();
try engine.submitTransferCommandBuffer(command_buffer, fence);
_ = try engine.device.waitForFences(1, @ptrCast(&fence), .true, std.math.maxInt(u64));
}

75
src/engine/atoms.zig Normal file
View File

@@ -0,0 +1,75 @@
pub const Atoms = @This();
const std = @import("std");
var allocator: std.mem.Allocator = undefined;
var string_arena: std.heap.ArenaAllocator = undefined;
var map: Map = undefined;
var array: Array = undefined;
var mutex: std.Thread.Mutex = undefined;
pub const Atom = enum(u32) { _ };
pub const Map = std.StringHashMapUnmanaged(Atom);
pub const Array = std.ArrayList([:0]const u8);
pub fn init(_allocator: std.mem.Allocator) void {
allocator = _allocator;
string_arena = .init(_allocator);
map = .{};
array = .empty;
mutex = .{};
}
pub fn deinit() void {
string_arena.deinit();
map.deinit(allocator);
array.deinit(allocator);
allocator = undefined;
string_arena = undefined;
map = undefined;
array = undefined;
mutex = undefined;
}
pub fn getString(atom: Atom) [:0]const u8 {
try mutex.lock();
defer mutex.unlock();
return array.items[atom.value];
}
pub fn getAtom(string: []const u8) ?Atom {
mutex.lock();
defer mutex.unlock();
return map.get(string);
}
pub fn getOrPutAtom(string: []const u8) !Atom {
mutex.lock();
defer mutex.unlock();
const entry = try map.getOrPut(allocator, string);
if (entry.found_existing) {
return entry.value_ptr.*;
} else {
errdefer _ = map.remove(string);
try array.ensureUnusedCapacity(allocator, 1);
const owned_string = try toOwnedString(string);
const atom: Atom = @enumFromInt(array.items.len);
entry.key_ptr.* = owned_string;
entry.value_ptr.* = atom;
array.appendAssumeCapacity(owned_string);
return atom;
}
}
fn toOwnedString(string: []const u8) ![:0]const u8 {
const owned_string = try string_arena.allocator().dupeZ(u8, string);
return owned_string;
}

View File

@@ -6,6 +6,7 @@ const vk = @import("vulkan");
const c = @import("const.zig"); const c = @import("const.zig");
const atoms = @import("engine/atoms.zig");
const Engine = @import("engine/Engine.zig"); const Engine = @import("engine/Engine.zig");
const Swapchain = @import("engine/Swapchain.zig"); const Swapchain = @import("engine/Swapchain.zig");
const Game = @import("Game.zig"); const Game = @import("Game.zig");
@@ -25,6 +26,9 @@ pub fn main() !void {
allocator = gpa.allocator(); allocator = gpa.allocator();
temp_allocator = fba.threadSafeAllocator(); temp_allocator = fba.threadSafeAllocator();
atoms.init(allocator);
defer atoms.deinit();
stbi.init(allocator); stbi.init(allocator);
defer stbi.deinit(); defer stbi.deinit();
@@ -52,9 +56,9 @@ pub fn main() !void {
defer engine.deinit(); defer engine.deinit();
var swapchain = try Swapchain.init(&engine); var swapchain = try Swapchain.init(&engine);
defer swapchain.deinit(); defer swapchain.deinit(&engine);
var game = try Game.init(allocator, &swapchain); var game = try Game.init(allocator, &engine, &swapchain);
defer game.deinit(); defer game.deinit();
var t1 = glfw.getTime(); var t1 = glfw.getTime();

View File

@@ -1,3 +1,5 @@
const std = @import("std");
const Vector3 = @import("Vector3.zig").Vector3; const Vector3 = @import("Vector3.zig").Vector3;
const Vector4 = @import("Vector3.zig").Vector4; const Vector4 = @import("Vector3.zig").Vector4;
@@ -28,6 +30,11 @@ pub const Vector2 = extern struct {
return self.vector; return self.vector;
} }
pub inline fn asArrayNorm(self: Vector2, comptime T: type) [2]T {
const scale_vector: Vector = @splat(std.math.maxInt(T));
return @as(@Vector(2, T), @intFromFloat(@round(self.vector * scale_vector)));
}
pub inline fn asVector3(self: Vector2, z: f32) Vector3 { pub inline fn asVector3(self: Vector2, z: f32) Vector3 {
const z_vector: @Vector(3, f32) = .{ undefined, undefined, z }; const z_vector: @Vector(3, f32) = .{ undefined, undefined, z };
return .{ .vector = @shuffle(f32, self.vector, z_vector, [_]i32{ 0, 1, ~@as(i32, 2) }) }; return .{ .vector = @shuffle(f32, self.vector, z_vector, [_]i32{ 0, 1, ~@as(i32, 2) }) };

View File

@@ -1,3 +1,5 @@
const std = @import("std");
const Quaternion = @import("Quaternion.zig").Quaternion; const Quaternion = @import("Quaternion.zig").Quaternion;
const Vector2 = @import("Vector2.zig").Vector2; const Vector2 = @import("Vector2.zig").Vector2;
const Vector4 = @import("Vector4.zig").Vector4; const Vector4 = @import("Vector4.zig").Vector4;
@@ -30,6 +32,11 @@ pub const Vector3 = extern struct {
return self.vector; return self.vector;
} }
pub inline fn asArrayNorm(self: Vector3, comptime T: type) [3]T {
const scale_vector: Vector = @splat(std.math.maxInt(T));
return @as(@Vector(3, T), @intFromFloat(@round(self.vector * scale_vector)));
}
pub inline fn asVector2(self: Vector3) Vector2 { pub inline fn asVector2(self: Vector3) Vector2 {
return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) }; return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) };
} }

View File

@@ -1,3 +1,5 @@
const std = @import("std");
const Vector2 = @import("Vector2.zig").Vector2; const Vector2 = @import("Vector2.zig").Vector2;
const Vector3 = @import("Vector3.zig").Vector3; const Vector3 = @import("Vector3.zig").Vector3;
@@ -30,6 +32,11 @@ pub const Vector4 = extern struct {
return self.vector; return self.vector;
} }
pub inline fn asArrayNorm(self: Vector4, comptime T: type) [4]T {
const scale_vector: Vector = @splat(std.math.maxInt(T));
return @as(@Vector(4, T), @intFromFloat(@round(self.vector * scale_vector)));
}
pub inline fn asVector2(self: Vector4) Vector2 { pub inline fn asVector2(self: Vector4) Vector2 {
return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) }; return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) };
} }

23
src/voxel.zig Normal file
View File

@@ -0,0 +1,23 @@
pub const Orientation = enum(u4) {
negative_x,
positive_x,
negative_y,
positive_y,
negative_z,
positive_z,
};
// ┌────────────────── x
// │ ┌────────────── y
// │ │ ┌───────── z
// │ │ │ ┌───── orientation
// │ │ │ │ ┌ material
// ┌┴─┐┌┴─┐ ┌┴─┐┌┴─┐ ┌┴──────────────┐
// 10987654 32109876 54321098 76543210
pub const Wall = packed struct(u32) {
material: u16,
orientation: Orientation,
z: u4,
y: u4,
x: u4,
};