Add uniform buffers

This commit is contained in:
Przemyslaw Gasinski 2024-07-22 00:27:24 +02:00
parent 8c60f79f7d
commit b1bbd65aaa
13 changed files with 5695 additions and 4 deletions

View file

@ -14,6 +14,8 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
}); });
// --- Dependencies ---
// Vulkan // Vulkan
const vkzig_dep = b.dependency("vulkan_zig", .{ const vkzig_dep = b.dependency("vulkan_zig", .{
.registry = @as([]const u8, b.pathFromRoot("./vk.xml")), .registry = @as([]const u8, b.pathFromRoot("./vk.xml")),
@ -35,6 +37,12 @@ pub fn build(b: *std.Build) void {
sdl_sdk.link(exe, .dynamic, .SDL2); sdl_sdk.link(exe, .dynamic, .SDL2);
exe.root_module.addImport("sdl2", sdl_sdk.getWrapperModuleVulkan(vkzig_bindings)); exe.root_module.addImport("sdl2", sdl_sdk.getWrapperModuleVulkan(vkzig_bindings));
// zmath
const zmath = b.dependency("zmath", .{});
exe.root_module.addImport("zmath", zmath.module("root"));
// ---
b.installArtifact(exe); b.installArtifact(exe);
const check = b.step("check", "Check if vulkan-test compiles"); const check = b.step("check", "Check if vulkan-test compiles");

View file

@ -4,6 +4,7 @@
.version = "0.1.0", .version = "0.1.0",
.dependencies = .{ .dependencies = .{
.zmath = .{ .path = "libs/zmath" },
.vulkan_zig = .{ .vulkan_zig = .{
.url = "https://github.com/Snektron/vulkan-zig/archive/9f6e6177b1fdb3ed22231d9216a24480e84cfa5e.tar.gz", .url = "https://github.com/Snektron/vulkan-zig/archive/9f6e6177b1fdb3ed22231d9216a24480e84cfa5e.tar.gz",
.hash = "1220f2961df224f7d35dee774b26194b8b937cc252fa8e4023407776c58521d53e38", .hash = "1220f2961df224f7d35dee774b26194b8b937cc252fa8e4023407776c58521d53e38",

22
libs/zmath/LICENSE Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2021 Michal Ziulek
Copyright (c) 2024 zig-gamedev contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

133
libs/zmath/README.md Normal file
View file

@ -0,0 +1,133 @@
# zmath v0.10.0 - SIMD math library for game developers
Tested on x86_64 and AArch64.
Provides ~140 optimized routines and ~70 extensive tests.
Can be used with any graphics API.
Documentation can be found [here](https://github.com/michal-z/zig-gamedev/blob/main/libs/zmath/src/zmath.zig).
Benchamrks can be found [here](https://github.com/michal-z/zig-gamedev/blob/main/libs/zmath/src/benchmark.zig).
An intro article can be found [here](https://zig.news/michalz/fast-multi-platform-simd-math-library-in-zig-2adn).
## Getting started
Copy `zmath` into a subdirectory of your project and add the following to your `build.zig.zon` .dependencies:
```zig
.zmath = .{ .path = "libs/zmath" },
```
Then in your `build.zig` add:
```zig
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{ ... });
const zmath = b.dependency("zmath", .{});
exe.root_module.addImport("zmath", zmath.module("root"));
}
```
Now in your code you may import and use zmath:
```zig
const zm = @import("zmath");
pub fn main() !void {
//
// OpenGL/Vulkan example
//
const object_to_world = zm.rotationY(..);
const world_to_view = zm.lookAtRh(
zm.f32x4(3.0, 3.0, 3.0, 1.0), // eye position
zm.f32x4(0.0, 0.0, 0.0, 1.0), // focus point
zm.f32x4(0.0, 1.0, 0.0, 0.0), // up direction ('w' coord is zero because this is a vector not a point)
);
// `perspectiveFovRhGl` produces Z values in [-1.0, 1.0] range (Vulkan app should use `perspectiveFovRh`)
const view_to_clip = zm.perspectiveFovRhGl(0.25 * math.pi, aspect_ratio, 0.1, 20.0);
const object_to_view = zm.mul(object_to_world, world_to_view);
const object_to_clip = zm.mul(object_to_view, view_to_clip);
// Transposition is needed because GLSL uses column-major matrices by default
gl.uniformMatrix4fv(0, 1, gl.TRUE, zm.arrNPtr(&object_to_clip));
// In GLSL: gl_Position = vec4(in_position, 1.0) * object_to_clip;
//
// DirectX example
//
const object_to_world = zm.rotationY(..);
const world_to_view = zm.lookAtLh(
zm.f32x4(3.0, 3.0, -3.0, 1.0), // eye position
zm.f32x4(0.0, 0.0, 0.0, 1.0), // focus point
zm.f32x4(0.0, 1.0, 0.0, 0.0), // up direction ('w' coord is zero because this is a vector not a point)
);
const view_to_clip = zm.perspectiveFovLh(0.25 * math.pi, aspect_ratio, 0.1, 20.0);
const object_to_view = zm.mul(object_to_world, world_to_view);
const object_to_clip = zm.mul(object_to_view, view_to_clip);
// Transposition is needed because HLSL uses column-major matrices by default
const mem = allocateUploadMemory(...);
zm.storeMat(mem, zm.transpose(object_to_clip));
// In HLSL: out_position_sv = mul(float4(in_position, 1.0), object_to_clip);
//
// 'WASD' camera movement example
//
{
const speed = zm.f32x4s(10.0);
const delta_time = zm.f32x4s(demo.frame_stats.delta_time);
const transform = zm.mul(zm.rotationX(demo.camera.pitch), zm.rotationY(demo.camera.yaw));
var forward = zm.normalize3(zm.mul(zm.f32x4(0.0, 0.0, 1.0, 0.0), transform));
zm.storeArr3(&demo.camera.forward, forward);
const right = speed * delta_time * zm.normalize3(zm.cross3(zm.f32x4(0.0, 1.0, 0.0, 0.0), forward));
forward = speed * delta_time * forward;
var cam_pos = zm.loadArr3(demo.camera.position);
if (keyDown('W')) {
cam_pos += forward;
} else if (keyDown('S')) {
cam_pos -= forward;
}
if (keyDown('D')) {
cam_pos += right;
} else if (keyDown('A')) {
cam_pos -= right;
}
zm.storeArr3(&demo.camera.position, cam_pos);
}
//
// SIMD wave equation solver example (works with vector width 4, 8 and 16)
// 'T' can be F32x4, F32x8 or F32x16
//
var z_index: i32 = 0;
while (z_index < grid_size) : (z_index += 1) {
const z = scale * @intToFloat(f32, z_index - grid_size / 2);
const vz = zm.splat(T, z);
var x_index: i32 = 0;
while (x_index < grid_size) : (x_index += zm.veclen(T)) {
const x = scale * @intToFloat(f32, x_index - grid_size / 2);
const vx = zm.splat(T, x) + voffset * zm.splat(T, scale);
const d = zm.sqrt(vx * vx + vz * vz);
const vy = zm.sin(d - vtime);
const index = @intCast(usize, x_index + z_index * grid_size);
zm.store(xslice[index..], vx, 0);
zm.store(yslice[index..], vy, 0);
zm.store(zslice[index..], vz, 0);
}
}
}
```

62
libs/zmath/build.zig Normal file
View file

@ -0,0 +1,62 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const options = .{
.optimize = b.option(
std.builtin.OptimizeMode,
"optimize",
"Select optimization mode",
) orelse b.standardOptimizeOption(.{
.preferred_optimize_mode = .ReleaseFast,
}),
.enable_cross_platform_determinism = b.option(
bool,
"enable_cross_platform_determinism",
"Enable cross-platform determinism",
) orelse true,
};
const options_step = b.addOptions();
inline for (std.meta.fields(@TypeOf(options))) |field| {
options_step.addOption(field.type, field.name, @field(options, field.name));
}
const options_module = options_step.createModule();
const zmath = b.addModule("root", .{
.root_source_file = b.path("src/main.zig"),
.imports = &.{
.{ .name = "zmath_options", .module = options_module },
},
});
const test_step = b.step("test", "Run zmath tests");
const tests = b.addTest(.{
.name = "zmath-tests",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = options.optimize,
});
b.installArtifact(tests);
tests.root_module.addImport("zmath_options", options_module);
test_step.dependOn(&b.addRunArtifact(tests).step);
const benchmark_step = b.step("benchmark", "Run zmath benchmarks");
const benchmarks = b.addExecutable(.{
.name = "zmath-benchmarks",
.root_source_file = b.path("src/benchmark.zig"),
.target = target,
.optimize = options.optimize,
});
b.installArtifact(benchmarks);
benchmarks.root_module.addImport("zmath", zmath);
benchmark_step.dependOn(&b.addRunArtifact(benchmarks).step);
}

10
libs/zmath/build.zig.zon Normal file
View file

@ -0,0 +1,10 @@
.{
.name = "zmath",
.version = "0.10.0",
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"README.md",
},
}

View file

@ -0,0 +1,469 @@
// -------------------------------------------------------------------------------------------------
// zmath - benchmarks
// -------------------------------------------------------------------------------------------------
// 'zig build benchmark -Doptimize=ReleaseFast' will build and benchmakrs with all optimisations.
//
// -------------------------------------------------------------------------------------------------
// 'AMD Ryzen 9 3950X 16-Core Processor', Windows 11, Zig 0.10.0-dev.2620+0e9458a3f, ReleaseFast
// -------------------------------------------------------------------------------------------------
// matrix mul benchmark (AOS) - scalar version: 1.5880s, zmath version: 1.0642s
// cross3, scale, bias benchmark (AOS) - scalar version: 0.9318s, zmath version: 0.6888s
// cross3, dot3, scale, bias benchmark (AOS) - scalar version: 1.2258s, zmath version: 1.1095s
// quaternion mul benchmark (AOS) - scalar version: 1.4123s, zmath version: 0.6958s
// wave benchmark (SOA) - scalar version: 4.8165s, zmath version: 0.7338s
//
// -------------------------------------------------------------------------------------------------
// 'AMD Ryzen 7 5800X 8-Core Processer', Linux 5.17.14, Zig 0.10.0-dev.2624+d506275a0, ReleaseFast
// -------------------------------------------------------------------------------------------------
// matrix mul benchmark (AOS) - scalar version: 1.3672s, zmath version: 0.8617s
// cross3, scale, bias benchmark (AOS) - scalar version: 0.6586s, zmath version: 0.4803s
// cross3, dot3, scale, bias benchmark (AOS) - scalar version: 1.0620s, zmath version: 0.8942s
// quaternion mul benchmark (AOS) - scalar version: 1.1324s, zmath version: 0.6064s
// wave benchmark (SOA) - scalar version: 3.6598s, zmath version: 0.4231s
//
// -------------------------------------------------------------------------------------------------
// 'Apple M1 Max', macOS Version 12.4, Zig 0.10.0-dev.2657+74442f350, ReleaseFast
// -------------------------------------------------------------------------------------------------
// matrix mul benchmark (AOS) - scalar version: 1.0297s, zmath version: 1.0538s
// cross3, scale, bias benchmark (AOS) - scalar version: 0.6294s, zmath version: 0.6532s
// cross3, dot3, scale, bias benchmark (AOS) - scalar version: 0.9807s, zmath version: 1.0988s
// quaternion mul benchmark (AOS) - scalar version: 1.5413s, zmath version: 0.7800s
// wave benchmark (SOA) - scalar version: 3.4220s, zmath version: 1.0255s
//
// -------------------------------------------------------------------------------------------------
// '11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz', Windows 11, Zig 0.10.0-dev.2620+0e9458a3f, ReleaseFast
// -------------------------------------------------------------------------------------------------
// matrix mul benchmark (AOS) - scalar version: 2.2308s, zmath version: 0.9376s
// cross3, scale, bias benchmark (AOS) - scalar version: 1.0821s, zmath version: 0.5110s
// cross3, dot3, scale, bias benchmark (AOS) - scalar version: 1.6580s, zmath version: 0.9167s
// quaternion mul benchmark (AOS) - scalar version: 2.0139s, zmath version: 0.5856s
// wave benchmark (SOA) - scalar version: 3.7832s, zmath version: 0.3642s
//
// -------------------------------------------------------------------------------------------------
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// m = mul(ma, mb); data set fits in L1 cache; AOS data layout.
try mat4MulBenchmark(allocator, 100_000);
// v = 0.01 * cross3(va, vb) + vec3(1.0); data set fits in L1 cache; AOS data layout.
try cross3ScaleBiasBenchmark(allocator, 10_000);
// v = dot3(va, vb) * (0.1 * cross3(va, vb) + vec3(1.0)); data set fits in L1 cache; AOS data layout.
try cross3Dot3ScaleBiasBenchmark(allocator, 10_000);
// q = qmul(qa, qb); data set fits in L1 cache; AOS data layout.
try quatBenchmark(allocator, 10_000);
// d = sqrt(x * x + z * z); y = sin(d - t); SOA layout.
try waveBenchmark(allocator, 1_000);
}
const std = @import("std");
const time = std.time;
const Timer = time.Timer;
const zm = @import("zmath");
var prng = std.Random.DefaultPrng.init(0);
const random = prng.random();
noinline fn mat4MulBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void {
std.debug.print("\n", .{});
std.debug.print("{s:>42} - ", .{"matrix mul benchmark (AOS)"});
var data0 = std.ArrayList([16]f32).init(allocator);
defer data0.deinit();
var data1 = std.ArrayList([16]f32).init(allocator);
defer data1.deinit();
var i: usize = 0;
while (i < 64) : (i += 1) {
try data0.append([16]f32{
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
});
try data1.append([16]f32{
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
random.float(f32), random.float(f32), random.float(f32), random.float(f32),
});
}
// Warmup, fills L1 cache.
i = 0;
while (i < 100) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const ma = zm.loadMat(a[0..]);
const mb = zm.loadMat(b[0..]);
const r = zm.mul(ma, mb);
std.mem.doNotOptimizeAway(&r);
}
}
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const r = [16]f32{
a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12],
a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13],
a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14],
a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15],
a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12],
a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13],
a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14],
a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15],
a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12],
a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13],
a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14],
a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15],
a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12],
a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13],
a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14],
a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15],
};
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s});
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const ma = zm.loadMat(a[0..]);
const mb = zm.loadMat(b[0..]);
const r = zm.mul(ma, mb);
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s});
}
}
noinline fn cross3ScaleBiasBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void {
std.debug.print("{s:>42} - ", .{"cross3, scale, bias benchmark (AOS)"});
var data0 = std.ArrayList([3]f32).init(allocator);
defer data0.deinit();
var data1 = std.ArrayList([3]f32).init(allocator);
defer data1.deinit();
var i: usize = 0;
while (i < 256) : (i += 1) {
try data0.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) });
try data1.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) });
}
// Warmup, fills L1 cache.
i = 0;
while (i < 100) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const va = zm.loadArr3(a);
const vb = zm.loadArr3(b);
const cp = zm.f32x4s(0.01) * zm.cross3(va, vb) + zm.f32x4s(1.0);
std.mem.doNotOptimizeAway(&cp);
}
}
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const r = [3]f32{
0.01 * (a[1] * b[2] - a[2] * b[1]) + 1.0,
0.01 * (a[2] * b[0] - a[0] * b[2]) + 1.0,
0.01 * (a[0] * b[1] - a[1] * b[0]) + 1.0,
};
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s});
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const va = zm.loadArr3(a);
const vb = zm.loadArr3(b);
const cp = zm.f32x4s(0.01) * zm.cross3(va, vb) + zm.f32x4s(1.0);
std.mem.doNotOptimizeAway(&cp);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s});
}
}
noinline fn cross3Dot3ScaleBiasBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void {
std.debug.print("{s:>42} - ", .{"cross3, dot3, scale, bias benchmark (AOS)"});
var data0 = std.ArrayList([3]f32).init(allocator);
defer data0.deinit();
var data1 = std.ArrayList([3]f32).init(allocator);
defer data1.deinit();
var i: usize = 0;
while (i < 256) : (i += 1) {
try data0.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) });
try data1.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) });
}
// Warmup, fills L1 cache.
i = 0;
while (i < 100) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const va = zm.loadArr3(a);
const vb = zm.loadArr3(b);
const r = (zm.dot3(va, vb) * (zm.f32x4s(0.1) * zm.cross3(va, vb) + zm.f32x4s(1.0)))[0];
std.mem.doNotOptimizeAway(&r);
}
}
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
const r = [3]f32{
d * (0.1 * (a[1] * b[2] - a[2] * b[1]) + 1.0),
d * (0.1 * (a[2] * b[0] - a[0] * b[2]) + 1.0),
d * (0.1 * (a[0] * b[1] - a[1] * b[0]) + 1.0),
};
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s});
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const va = zm.loadArr3(a);
const vb = zm.loadArr3(b);
const r = zm.dot3(va, vb) * (zm.f32x4s(0.1) * zm.cross3(va, vb) + zm.f32x4s(1.0));
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s});
}
}
noinline fn quatBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void {
std.debug.print("{s:>42} - ", .{"quaternion mul benchmark (AOS)"});
var data0 = std.ArrayList([4]f32).init(allocator);
defer data0.deinit();
var data1 = std.ArrayList([4]f32).init(allocator);
defer data1.deinit();
var i: usize = 0;
while (i < 256) : (i += 1) {
try data0.append([4]f32{ random.float(f32), random.float(f32), random.float(f32), random.float(f32) });
try data1.append([4]f32{ random.float(f32), random.float(f32), random.float(f32), random.float(f32) });
}
// Warmup, fills L1 cache.
i = 0;
while (i < 100) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const va = zm.loadArr4(a);
const vb = zm.loadArr4(b);
const r = zm.qmul(va, vb);
std.mem.doNotOptimizeAway(&r);
}
}
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const r = [4]f32{
(b[3] * a[0]) + (b[0] * a[3]) + (b[1] * a[2]) - (b[2] * a[1]),
(b[3] * a[1]) - (b[0] * a[2]) + (b[1] * a[3]) + (b[2] * a[0]),
(b[3] * a[2]) + (b[0] * a[1]) - (b[1] * a[0]) + (b[2] * a[3]),
(b[3] * a[3]) - (b[0] * a[0]) - (b[1] * a[1]) - (b[2] * a[2]),
};
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s});
}
{
i = 0;
var timer = try Timer.start();
const start = timer.lap();
while (i < count) : (i += 1) {
for (data1.items) |b| {
for (data0.items) |a| {
const va = zm.loadArr4(a);
const vb = zm.loadArr4(b);
const r = zm.qmul(va, vb);
std.mem.doNotOptimizeAway(&r);
}
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s});
}
}
noinline fn waveBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void {
_ = allocator;
std.debug.print("{s:>42} - ", .{"wave benchmark (SOA)"});
const grid_size = 1024;
{
var t: f32 = 0.0;
const scale: f32 = 0.05;
var timer = try Timer.start();
const start = timer.lap();
var iter: usize = 0;
while (iter < count) : (iter += 1) {
var z_index: i32 = 0;
while (z_index < grid_size) : (z_index += 1) {
const z = scale * @as(f32, @floatFromInt(z_index - grid_size / 2));
var x_index: i32 = 0;
while (x_index < grid_size) : (x_index += 4) {
const x0 = scale * @as(f32, @floatFromInt(x_index + 0 - grid_size / 2));
const x1 = scale * @as(f32, @floatFromInt(x_index + 1 - grid_size / 2));
const x2 = scale * @as(f32, @floatFromInt(x_index + 2 - grid_size / 2));
const x3 = scale * @as(f32, @floatFromInt(x_index + 3 - grid_size / 2));
const d0 = zm.sqrt(x0 * x0 + z * z);
const d1 = zm.sqrt(x1 * x1 + z * z);
const d2 = zm.sqrt(x2 * x2 + z * z);
const d3 = zm.sqrt(x3 * x3 + z * z);
const y0 = zm.sin(d0 - t);
const y1 = zm.sin(d1 - t);
const y2 = zm.sin(d2 - t);
const y3 = zm.sin(d3 - t);
std.mem.doNotOptimizeAway(&y0);
std.mem.doNotOptimizeAway(&y1);
std.mem.doNotOptimizeAway(&y2);
std.mem.doNotOptimizeAway(&y3);
}
}
t += 0.001;
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s});
}
{
const T = zm.F32x16;
const static = struct {
const offsets = [16]f32{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
};
const voffset = zm.load(static.offsets[0..], T, 0);
var vt = zm.splat(T, 0.0);
const scale: f32 = 0.05;
var timer = try Timer.start();
const start = timer.lap();
var iter: usize = 0;
while (iter < count) : (iter += 1) {
var z_index: i32 = 0;
while (z_index < grid_size) : (z_index += 1) {
const z = scale * @as(f32, @floatFromInt(z_index - grid_size / 2));
const vz = zm.splat(T, z);
var x_index: i32 = 0;
while (x_index < grid_size) : (x_index += zm.veclen(T)) {
const x = scale * @as(f32, @floatFromInt(x_index - grid_size / 2));
const vx = zm.splat(T, x) + voffset * zm.splat(T, scale);
const d = zm.sqrt(vx * vx + vz * vz);
const vy = zm.sin(d - vt);
std.mem.doNotOptimizeAway(&vy);
}
}
vt += zm.splat(T, 0.001);
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s});
}
}

16
libs/zmath/src/main.zig Normal file
View file

@ -0,0 +1,16 @@
//--------------------------------------------------------------------------------------------------
//
// SIMD math library for game developers
// https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath
//
// See zmath.zig for more details.
// See util.zig for additional functionality.
//
//--------------------------------------------------------------------------------------------------
pub usingnamespace @import("zmath.zig");
pub const util = @import("util.zig");
// ensure transitive closure of test coverage
comptime {
_ = util;
}

188
libs/zmath/src/util.zig Normal file
View file

@ -0,0 +1,188 @@
// ==============================================================================
//
// Collection of useful functions building on top of, and extending, core zmath.
// https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath
//
// ------------------------------------------------------------------------------
// 1. Matrix functions
// ------------------------------------------------------------------------------
//
// As an example, in a left handed Y-up system:
// getAxisX is equivalent to the right vector
// getAxisY is equivalent to the up vector
// getAxisZ is equivalent to the forward vector
//
// getTranslationVec(m: Mat) Vec
// getAxisX(m: Mat) Vec
// getAxisY(m: Mat) Vec
// getAxisZ(m: Mat) Vec
//
// ==============================================================================
const zm = @import("zmath.zig");
const std = @import("std");
const math = std.math;
const expect = std.testing.expect;
pub fn getTranslationVec(m: zm.Mat) zm.Vec {
var translation = m[3];
translation[3] = 0;
return translation;
}
pub fn setTranslationVec(m: *zm.Mat, translation: zm.Vec) void {
const w = m[3][3];
m[3] = translation;
m[3][3] = w;
}
pub fn getScaleVec(m: zm.Mat) zm.Vec {
const scale_x = zm.length3(zm.f32x4(m[0][0], m[1][0], m[2][0], 0))[0];
const scale_y = zm.length3(zm.f32x4(m[0][1], m[1][1], m[2][1], 0))[0];
const scale_z = zm.length3(zm.f32x4(m[0][2], m[1][2], m[2][2], 0))[0];
return zm.f32x4(scale_x, scale_y, scale_z, 0);
}
pub fn getRotationQuat(_m: zm.Mat) zm.Quat {
// Ortho normalize given matrix.
const c1 = zm.normalize3(zm.f32x4(_m[0][0], _m[1][0], _m[2][0], 0));
const c2 = zm.normalize3(zm.f32x4(_m[0][1], _m[1][1], _m[2][1], 0));
const c3 = zm.normalize3(zm.f32x4(_m[0][2], _m[1][2], _m[2][2], 0));
var m = _m;
m[0][0] = c1[0];
m[1][0] = c1[1];
m[2][0] = c1[2];
m[0][1] = c2[0];
m[1][1] = c2[1];
m[2][1] = c2[2];
m[0][2] = c3[0];
m[1][2] = c3[1];
m[2][2] = c3[2];
// Extract rotation
return zm.quatFromMat(m);
}
pub fn getAxisX(m: zm.Mat) zm.Vec {
return zm.normalize3(zm.f32x4(m[0][0], m[0][1], m[0][2], 0.0));
}
pub fn getAxisY(m: zm.Mat) zm.Vec {
return zm.normalize3(zm.f32x4(m[1][0], m[1][1], m[1][2], 0.0));
}
pub fn getAxisZ(m: zm.Mat) zm.Vec {
return zm.normalize3(zm.f32x4(m[2][0], m[2][1], m[2][2], 0.0));
}
test "zmath.util.mat.translation" {
// zig fmt: off
const mat_data = [18]f32{
1.0,
2.0, 3.0, 4.0, 5.0,
6.0, 7.0, 8.0, 9.0,
10.0,11.0, 12.0,13.0,
14.0, 15.0, 16.0, 17.0,
18.0,
};
// zig fmt: on
const mat = zm.loadMat(mat_data[1..]);
const translation = getTranslationVec(mat);
try zm.expectVecApproxEqAbs(translation, zm.f32x4(14.0, 15.0, 16.0, 0.0), 0.0001);
}
test "zmath.util.mat.scale" {
const mat = zm.mul(zm.scaling(3, 4, 5), zm.translation(6, 7, 8));
const scale = getScaleVec(mat);
try zm.expectVecApproxEqAbs(scale, zm.f32x4(3.0, 4.0, 5.0, 0.0), 0.0001);
}
test "zmath.util.mat.rotation" {
const rotate_origin = zm.matFromRollPitchYaw(0.1, 1.2, 2.3);
const mat = zm.mul(zm.mul(rotate_origin, zm.scaling(3, 4, 5)), zm.translation(6, 7, 8));
const rotate_get = getRotationQuat(mat);
const v0 = zm.mul(zm.f32x4s(1), rotate_origin);
const v1 = zm.mul(zm.f32x4s(1), zm.quatToMat(rotate_get));
try zm.expectVecApproxEqAbs(v0, v1, 0.0001);
}
test "zmath.util.mat.z_vec" {
const degToRad = std.math.degreesToRadians;
var identity = zm.identity();
var z_vec = getAxisZ(identity);
try zm.expectVecApproxEqAbs(z_vec, zm.f32x4(0.0, 0.0, 1.0, 0), 0.0001);
const rot_yaw = zm.rotationY(degToRad(90));
identity = zm.mul(identity, rot_yaw);
z_vec = getAxisZ(identity);
try zm.expectVecApproxEqAbs(z_vec, zm.f32x4(1.0, 0.0, 0.0, 0), 0.0001);
}
test "zmath.util.mat.y_vec" {
const degToRad = std.math.degreesToRadians;
var identity = zm.identity();
var y_vec = getAxisY(identity);
try zm.expectVecApproxEqAbs(y_vec, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01);
const rot_yaw = zm.rotationY(degToRad(90));
identity = zm.mul(identity, rot_yaw);
y_vec = getAxisY(identity);
try zm.expectVecApproxEqAbs(y_vec, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01);
const rot_pitch = zm.rotationX(degToRad(90));
identity = zm.mul(identity, rot_pitch);
y_vec = getAxisY(identity);
try zm.expectVecApproxEqAbs(y_vec, zm.f32x4(0.0, 0.0, 1.0, 0), 0.01);
}
test "zmath.util.mat.right" {
const degToRad = std.math.degreesToRadians;
var identity = zm.identity();
var right = getAxisX(identity);
try zm.expectVecApproxEqAbs(right, zm.f32x4(1.0, 0.0, 0.0, 0), 0.01);
const rot_yaw = zm.rotationY(degToRad(90));
identity = zm.mul(identity, rot_yaw);
right = getAxisX(identity);
try zm.expectVecApproxEqAbs(right, zm.f32x4(0.0, 0.0, -1.0, 0), 0.01);
const rot_pitch = zm.rotationX(degToRad(90));
identity = zm.mul(identity, rot_pitch);
right = getAxisX(identity);
try zm.expectVecApproxEqAbs(right, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01);
}
// ------------------------------------------------------------------------------
// This software is available under 2 licenses -- choose whichever you prefer.
// ------------------------------------------------------------------------------
// ALTERNATIVE A - MIT License
// Copyright (c) 2022 Michal Ziulek and Contributors
// Permission is hereby granted, free of charge, to any person obtaining identity copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ------------------------------------------------------------------------------
// ALTERNATIVE B - Public Domain (www.unlicense.org)
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
// software, either in source code form or as identity compiled binary, for any purpose,
// commercial or non-commercial, and by any means.
// In jurisdictions that recognize copyright laws, the author or authors of this
// software dedicate any and all copyright interest in the software to the public
// domain. We make this dedication for the benefit of the public at large and to
// the detriment of our heirs and successors. We intend this dedication to be an
// overt act of relinquishment in perpetuity of all present and future rights to
// this software under copyright law.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------

4568
libs/zmath/src/zmath.zig Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,10 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const sdl = @import("sdl2"); const sdl = @import("sdl2");
const zm = @import("zmath");
const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer; const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer;
const Vector3 = @import("utilities.zig").Vector3;
pub fn main() !void { pub fn main() !void {
const window = try initWindow(); const window = try initWindow();
@ -16,6 +19,11 @@ pub fn main() !void {
var vulkan_renderer = try VulkanRenderer.init(window, allocator); var vulkan_renderer = try VulkanRenderer.init(window, allocator);
defer vulkan_renderer.deinit(); defer vulkan_renderer.deinit();
var angle: f32 = 0.0;
var now: u64 = sdl.getPerformanceCounter();
var last: u64 = 0;
var delta_time: f32 = 0.0;
mainLoop: while (true) { mainLoop: while (true) {
while (sdl.pollEvent()) |ev| { while (sdl.pollEvent()) |ev| {
switch (ev) { switch (ev) {
@ -29,6 +37,18 @@ pub fn main() !void {
} }
} }
last = now;
now = sdl.getPerformanceCounter();
delta_time = @as(f32, @floatFromInt(now - last)) * 1000.0 / @as(f32, @floatFromInt(now));
angle += 10.0 * delta_time;
if (angle > 360.0) {
angle -= 360.0;
}
try vulkan_renderer.updateModel(zm.rotationZ(angle));
try vulkan_renderer.draw(); try vulkan_renderer.draw();
} }
} }

View file

@ -3,9 +3,15 @@
layout(location = 0) in vec3 pos; layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 col; layout(location = 1) in vec3 col;
layout(binding = 0) uniform MVP {
mat4 projection;
mat4 view;
mat4 model;
} mvp;
layout(location = 0) out vec3 fragCol; layout(location = 0) out vec3 fragCol;
void main() { void main() {
gl_Position = vec4(pos, 1.0); gl_Position = mvp.projection * mvp.view * mvp.model * vec4(pos, 1.0);
fragCol = col; fragCol = col;
} }

View file

@ -3,6 +3,7 @@ const sdl = @import("sdl2");
const vk = @import("vulkan"); const vk = @import("vulkan");
const builtin = @import("builtin"); const builtin = @import("builtin");
const shaders = @import("shaders"); const shaders = @import("shaders");
const zm = @import("zmath");
const Utilities = @import("utilities.zig"); const Utilities = @import("utilities.zig");
const QueueFamilyIndices = Utilities.QueueFamilyIndices; const QueueFamilyIndices = Utilities.QueueFamilyIndices;
@ -39,6 +40,12 @@ pub const CommandBuffer = vk.CommandBufferProxy(apis);
pub const VulkanRenderer = struct { pub const VulkanRenderer = struct {
const Self = @This(); const Self = @This();
const Mvp = struct {
projection: zm.Mat,
view: zm.Mat,
model: zm.Mat,
};
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
vkb: BaseDispatch, vkb: BaseDispatch,
@ -50,6 +57,9 @@ pub const VulkanRenderer = struct {
// Scene objects // Scene objects
meshes: [2]Mesh, meshes: [2]Mesh,
// Scene settings
mvp: Mvp,
// Main // Main
instance: Instance, instance: Instance,
physical_device: vk.PhysicalDevice, physical_device: vk.PhysicalDevice,
@ -65,6 +75,15 @@ pub const VulkanRenderer = struct {
swapchain_framebuffers: []vk.Framebuffer, swapchain_framebuffers: []vk.Framebuffer,
command_buffers: []CommandBuffer, command_buffers: []CommandBuffer,
// Descriptors
descriptor_set_layout: vk.DescriptorSetLayout,
descriptor_pool: vk.DescriptorPool,
descriptor_sets: []vk.DescriptorSet,
uniform_buffer: []vk.Buffer,
uniform_buffer_memory: []vk.DeviceMemory,
// Pipeline // Pipeline
graphics_pipeline: vk.Pipeline, graphics_pipeline: vk.Pipeline,
pipeline_layout: vk.PipelineLayout, pipeline_layout: vk.PipelineLayout,
@ -103,10 +122,28 @@ pub const VulkanRenderer = struct {
try self.createLogicalDevice(); try self.createLogicalDevice();
try self.createSwapchain(); try self.createSwapchain();
try self.createRenderPass(); try self.createRenderPass();
try self.createDescriptorSetLayout();
try self.createGraphicsPipeline(); try self.createGraphicsPipeline();
try self.createFramebuffers(); try self.createFramebuffers();
try self.createCommandPool(); try self.createCommandPool();
const aspect: f32 = @as(f32, @floatFromInt(self.extent.width)) / @as(f32, @floatFromInt(self.extent.height));
self.mvp.projection = zm.perspectiveFovRh(
std.math.degreesToRadians(45.0),
aspect,
0.1,
100.0,
);
self.mvp.view = zm.lookAtRh(
zm.Vec{ 0.0, 0.0, 2.0, 0.0 },
zm.Vec{ 0.0, 0.0, 0.0, 0.0 },
zm.Vec{ 0.0, 1.0, 0.0, 0.0 },
);
self.mvp.model = zm.identity();
// Invert y scale
self.mvp.projection[1][1] *= -1;
// Create meshes // Create meshes
// Vertex Data // Vertex Data
var mesh_vertices = [_]Vertex{ var mesh_vertices = [_]Vertex{
@ -154,12 +191,21 @@ pub const VulkanRenderer = struct {
self.meshes = [_]Mesh{ first_mesh, second_mesh }; self.meshes = [_]Mesh{ first_mesh, second_mesh };
try self.createCommandBuffers(); try self.createCommandBuffers();
try self.createUniformBuffers();
try self.createDescriptorPool();
try self.createDescriptorSets();
try self.recordCommands(); try self.recordCommands();
try self.createSynchronisation(); try self.createSynchronisation();
return self; return self;
} }
pub fn updateModel(self: *Self, new_model: zm.Mat) !void {
self.mvp.model = new_model;
}
pub fn draw(self: *Self) !void { pub fn draw(self: *Self) !void {
// Wait for given fence to signal (open) from last draw before continuing // Wait for given fence to signal (open) from last draw before continuing
_ = try self.device.waitForFences(1, @ptrCast(&self.draw_fences[self.current_frame]), vk.TRUE, std.math.maxInt(u64)); _ = try self.device.waitForFences(1, @ptrCast(&self.draw_fences[self.current_frame]), vk.TRUE, std.math.maxInt(u64));
@ -175,6 +221,8 @@ pub const VulkanRenderer = struct {
.null_handle, .null_handle,
); );
try self.updateUniformBuffer(image_index_result.image_index);
// -- Submit command buffer to render // -- Submit command buffer to render
// Queue submission information // Queue submission information
const wait_stages = [_]vk.PipelineStageFlags{.{ .color_attachment_output_bit = true }}; const wait_stages = [_]vk.PipelineStageFlags{.{ .color_attachment_output_bit = true }};
@ -215,6 +263,17 @@ pub const VulkanRenderer = struct {
self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null);
} }
self.device.destroyDescriptorPool(self.descriptor_pool, null);
self.device.destroyDescriptorSetLayout(self.descriptor_set_layout, null);
for (self.uniform_buffer, self.uniform_buffer_memory) |buffer, buffer_memory| {
self.device.destroyBuffer(buffer, null);
self.device.freeMemory(buffer_memory, null);
}
self.allocator.free(self.uniform_buffer);
self.allocator.free(self.uniform_buffer_memory);
self.allocator.free(self.descriptor_sets);
for (self.meshes) |mesh| { for (self.meshes) |mesh| {
mesh.destroyBuffers(); mesh.destroyBuffers();
} }
@ -491,6 +550,26 @@ pub const VulkanRenderer = struct {
self.render_pass = try self.device.createRenderPass(&render_pass_create_info, null); self.render_pass = try self.device.createRenderPass(&render_pass_create_info, null);
} }
fn createDescriptorSetLayout(self: *Self) !void {
// MVP binding info
const mvp_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 0, // Binding point in shader (designated by binding number in shader)
.descriptor_type = .uniform_buffer, // Type of descriptor (unifor, dynamic uniform, image sampler, etc)
.descriptor_count = 1, // Number of descriptors for binding
.stage_flags = .{ .vertex_bit = true }, // Shader stage to bind to
.p_immutable_samplers = null, // For texture: can make smapler data immutable by specifying in layout
};
// Create descriptor set layout with given bindings
const layout_create_info: vk.DescriptorSetLayoutCreateInfo = .{
.binding_count = 1, // Number of binding infos
.p_bindings = @ptrCast(&mvp_layout_binding), // Array of binding infos
};
// Create descriptor set layout
self.descriptor_set_layout = try self.device.createDescriptorSetLayout(&layout_create_info, null);
}
fn createGraphicsPipeline(self: *Self) !void { fn createGraphicsPipeline(self: *Self) !void {
// Create shader modules // Create shader modules
const vert = try self.device.createShaderModule(&.{ const vert = try self.device.createShaderModule(&.{
@ -606,7 +685,7 @@ pub const VulkanRenderer = struct {
.polygon_mode = .fill, // How to handle filling points between vertices .polygon_mode = .fill, // How to handle filling points between vertices
.line_width = 1.0, // How thick the lines should be when drawn .line_width = 1.0, // How thick the lines should be when drawn
.cull_mode = .{ .back_bit = true }, // Which face of a triangle to cull .cull_mode = .{ .back_bit = true }, // Which face of a triangle to cull
.front_face = .clockwise, // Winding to determine which side is front .front_face = .counter_clockwise, // Winding to determine which side is front
.depth_bias_enable = vk.FALSE, // Whether to add depth bias to fragments (good for stopping "shadow acne" in shadow mapping) .depth_bias_enable = vk.FALSE, // Whether to add depth bias to fragments (good for stopping "shadow acne" in shadow mapping)
.depth_bias_constant_factor = 0, .depth_bias_constant_factor = 0,
.depth_bias_clamp = 0, .depth_bias_clamp = 0,
@ -647,8 +726,11 @@ pub const VulkanRenderer = struct {
.blend_constants = [_]f32{ 0, 0, 0, 0 }, .blend_constants = [_]f32{ 0, 0, 0, 0 },
}; };
// -- Pipeline layout (TODO: Apply future descriptor set layouts) -- // -- Pipeline layout --
const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{}; const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{
.set_layout_count = 1,
.p_set_layouts = @ptrCast(&self.descriptor_set_layout),
};
self.pipeline_layout = try self.device.createPipelineLayout(&pipeline_layout_create_info, null); self.pipeline_layout = try self.device.createPipelineLayout(&pipeline_layout_create_info, null);
@ -748,6 +830,101 @@ pub const VulkanRenderer = struct {
} }
} }
fn createUniformBuffers(self: *Self) !void {
// Buffer size will be size of all three variables (will offset to access)
const buffer_size: vk.DeviceSize = @sizeOf(@TypeOf(self.mvp));
// One uniform buffer for each image (and by extension, command buffer)
self.uniform_buffer = try self.allocator.alloc(vk.Buffer, self.swapchain_images.len);
self.uniform_buffer_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain_images.len);
// Create the uniform buffers
for (0..self.uniform_buffer.len) |i| {
try Utilities.createBuffer(
self.physical_device,
self.instance,
self.device,
buffer_size,
.{ .uniform_buffer_bit = true },
.{ .host_visible_bit = true, .host_coherent_bit = true },
&self.uniform_buffer[i],
&self.uniform_buffer_memory[i],
);
}
}
fn createDescriptorPool(self: *Self) !void {
// Type of descriptors + how many descriptors (!= descriptor sets) (combined makes the pool size)
const pool_size: vk.DescriptorPoolSize = .{
.type = .uniform_buffer,
.descriptor_count = @intCast(self.uniform_buffer.len),
};
// Data to create descriptor pool
const pool_create_info: vk.DescriptorPoolCreateInfo = .{
.max_sets = @intCast(self.uniform_buffer.len), // Maximum number of descriptor sets that can be created from pool
.pool_size_count = 1, // Amount of pool sizes being passed
.p_pool_sizes = @ptrCast(&pool_size), // Pool sizes to create pool with
};
// Create descriptor pool
self.descriptor_pool = try self.device.createDescriptorPool(&pool_create_info, null);
}
fn createDescriptorSets(self: *Self) !void {
// One descriptor set for every buffer
self.descriptor_sets = try self.allocator.alloc(vk.DescriptorSet, self.uniform_buffer.len);
var set_layouts = try self.allocator.alloc(vk.DescriptorSetLayout, self.uniform_buffer.len);
defer self.allocator.free(set_layouts);
for (0..set_layouts.len) |i| {
set_layouts[i] = self.descriptor_set_layout;
}
// Descriptor set allocation info
const set_alloc_info: vk.DescriptorSetAllocateInfo = .{
.descriptor_pool = self.descriptor_pool, // Pool to allocate descriptor set from
.descriptor_set_count = @intCast(self.descriptor_sets.len), // Number of sets to allocate
.p_set_layouts = set_layouts.ptr, // Layouts to use to allocate sets (1:1 relationship)
};
// Allocate descriptor sets (multiple)
try self.device.allocateDescriptorSets(&set_alloc_info, self.descriptor_sets.ptr);
// Update all of descriptor set buffer bindings
for (0..self.descriptor_sets.len) |i| {
// Buffer info and data offset info
const mvp_buffer_info: vk.DescriptorBufferInfo = .{
.buffer = self.uniform_buffer[i], // Bufer to get data from
.offset = 0, // Position of start of data
.range = @sizeOf(@TypeOf(self.mvp)), // Size of data
};
// Data about connection between binding and buffer
const mvp_set_write: vk.WriteDescriptorSet = .{
.dst_set = self.descriptor_sets[i], // Descriptor set to update
.dst_binding = 0, // Binding to update (matches with binding on layout/shader)
.dst_array_element = 0, // Index in array to update
.descriptor_type = .uniform_buffer, // Type of descriptor
.descriptor_count = 1, // Amount to update
.p_buffer_info = @ptrCast(&mvp_buffer_info), // Information about buffer data to bind
.p_image_info = undefined,
.p_texel_buffer_view = undefined,
};
// Update the descriptor sets with new buffer/binding info
self.device.updateDescriptorSets(1, @ptrCast(&mvp_set_write), 0, null);
}
}
fn updateUniformBuffer(self: Self, image_index: u32) !void {
const data = try self.device.mapMemory(self.uniform_buffer_memory[image_index], 0, @sizeOf(Mvp), .{});
const mvp_data: *Mvp = @ptrCast(@alignCast(data));
mvp_data.* = self.mvp;
self.device.unmapMemory(self.uniform_buffer_memory[image_index]);
}
fn recordCommands(self: *Self) !void { fn recordCommands(self: *Self) !void {
// Information about how to begin each command // Information about how to begin each command
const buffer_begin_info: vk.CommandBufferBeginInfo = .{ const buffer_begin_info: vk.CommandBufferBeginInfo = .{
@ -799,6 +976,17 @@ pub const VulkanRenderer = struct {
// Bind mesh index buffer, with 0 offset and using the uint32 type // Bind mesh index buffer, with 0 offset and using the uint32 type
command_buffer.bindIndexBuffer(mesh.index_buffer, 0, .uint32); command_buffer.bindIndexBuffer(mesh.index_buffer, 0, .uint32);
// Bind descriptor sets
command_buffer.bindDescriptorSets(
.graphics,
self.pipeline_layout,
0,
1,
@ptrCast(&self.descriptor_sets[i]),
0,
null,
);
// Execute a pipeline // Execute a pipeline
command_buffer.drawIndexed(mesh.index_count, 1, 0, 0, 0); command_buffer.drawIndexed(mesh.index_count, 1, 0, 0, 0);
} }