Add uniform buffers
This commit is contained in:
parent
8c60f79f7d
commit
b1bbd65aaa
13 changed files with 5695 additions and 4 deletions
|
@ -14,6 +14,8 @@ pub fn build(b: *std.Build) void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// --- Dependencies ---
|
||||
|
||||
// Vulkan
|
||||
const vkzig_dep = b.dependency("vulkan_zig", .{
|
||||
.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);
|
||||
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);
|
||||
|
||||
const check = b.step("check", "Check if vulkan-test compiles");
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
.version = "0.1.0",
|
||||
|
||||
.dependencies = .{
|
||||
.zmath = .{ .path = "libs/zmath" },
|
||||
.vulkan_zig = .{
|
||||
.url = "https://github.com/Snektron/vulkan-zig/archive/9f6e6177b1fdb3ed22231d9216a24480e84cfa5e.tar.gz",
|
||||
.hash = "1220f2961df224f7d35dee774b26194b8b937cc252fa8e4023407776c58521d53e38",
|
||||
|
|
22
libs/zmath/LICENSE
Normal file
22
libs/zmath/LICENSE
Normal 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
133
libs/zmath/README.md
Normal 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
62
libs/zmath/build.zig
Normal 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
10
libs/zmath/build.zig.zon
Normal file
|
@ -0,0 +1,10 @@
|
|||
.{
|
||||
.name = "zmath",
|
||||
.version = "0.10.0",
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
"README.md",
|
||||
},
|
||||
}
|
469
libs/zmath/src/benchmark.zig
Normal file
469
libs/zmath/src/benchmark.zig
Normal 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
16
libs/zmath/src/main.zig
Normal 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
188
libs/zmath/src/util.zig
Normal 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
4568
libs/zmath/src/zmath.zig
Normal file
File diff suppressed because it is too large
Load diff
20
src/main.zig
20
src/main.zig
|
@ -1,7 +1,10 @@
|
|||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const sdl = @import("sdl2");
|
||||
const zm = @import("zmath");
|
||||
|
||||
const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer;
|
||||
const Vector3 = @import("utilities.zig").Vector3;
|
||||
|
||||
pub fn main() !void {
|
||||
const window = try initWindow();
|
||||
|
@ -16,6 +19,11 @@ pub fn main() !void {
|
|||
var vulkan_renderer = try VulkanRenderer.init(window, allocator);
|
||||
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) {
|
||||
while (sdl.pollEvent()) |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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
layout(location = 0) in vec3 pos;
|
||||
layout(location = 1) in vec3 col;
|
||||
|
||||
layout(binding = 0) uniform MVP {
|
||||
mat4 projection;
|
||||
mat4 view;
|
||||
mat4 model;
|
||||
} mvp;
|
||||
|
||||
layout(location = 0) out vec3 fragCol;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(pos, 1.0);
|
||||
gl_Position = mvp.projection * mvp.view * mvp.model * vec4(pos, 1.0);
|
||||
fragCol = col;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ const sdl = @import("sdl2");
|
|||
const vk = @import("vulkan");
|
||||
const builtin = @import("builtin");
|
||||
const shaders = @import("shaders");
|
||||
const zm = @import("zmath");
|
||||
|
||||
const Utilities = @import("utilities.zig");
|
||||
const QueueFamilyIndices = Utilities.QueueFamilyIndices;
|
||||
|
@ -39,6 +40,12 @@ pub const CommandBuffer = vk.CommandBufferProxy(apis);
|
|||
pub const VulkanRenderer = struct {
|
||||
const Self = @This();
|
||||
|
||||
const Mvp = struct {
|
||||
projection: zm.Mat,
|
||||
view: zm.Mat,
|
||||
model: zm.Mat,
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
vkb: BaseDispatch,
|
||||
|
@ -50,6 +57,9 @@ pub const VulkanRenderer = struct {
|
|||
// Scene objects
|
||||
meshes: [2]Mesh,
|
||||
|
||||
// Scene settings
|
||||
mvp: Mvp,
|
||||
|
||||
// Main
|
||||
instance: Instance,
|
||||
physical_device: vk.PhysicalDevice,
|
||||
|
@ -65,6 +75,15 @@ pub const VulkanRenderer = struct {
|
|||
swapchain_framebuffers: []vk.Framebuffer,
|
||||
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
|
||||
graphics_pipeline: vk.Pipeline,
|
||||
pipeline_layout: vk.PipelineLayout,
|
||||
|
@ -103,10 +122,28 @@ pub const VulkanRenderer = struct {
|
|||
try self.createLogicalDevice();
|
||||
try self.createSwapchain();
|
||||
try self.createRenderPass();
|
||||
try self.createDescriptorSetLayout();
|
||||
try self.createGraphicsPipeline();
|
||||
try self.createFramebuffers();
|
||||
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
|
||||
// Vertex Data
|
||||
var mesh_vertices = [_]Vertex{
|
||||
|
@ -154,12 +191,21 @@ pub const VulkanRenderer = struct {
|
|||
self.meshes = [_]Mesh{ first_mesh, second_mesh };
|
||||
|
||||
try self.createCommandBuffers();
|
||||
|
||||
try self.createUniformBuffers();
|
||||
try self.createDescriptorPool();
|
||||
try self.createDescriptorSets();
|
||||
|
||||
try self.recordCommands();
|
||||
try self.createSynchronisation();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn updateModel(self: *Self, new_model: zm.Mat) !void {
|
||||
self.mvp.model = new_model;
|
||||
}
|
||||
|
||||
pub fn draw(self: *Self) !void {
|
||||
// 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));
|
||||
|
@ -175,6 +221,8 @@ pub const VulkanRenderer = struct {
|
|||
.null_handle,
|
||||
);
|
||||
|
||||
try self.updateUniformBuffer(image_index_result.image_index);
|
||||
|
||||
// -- Submit command buffer to render
|
||||
// Queue submission information
|
||||
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.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| {
|
||||
mesh.destroyBuffers();
|
||||
}
|
||||
|
@ -491,6 +550,26 @@ pub const VulkanRenderer = struct {
|
|||
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 {
|
||||
// Create shader modules
|
||||
const vert = try self.device.createShaderModule(&.{
|
||||
|
@ -606,7 +685,7 @@ pub const VulkanRenderer = struct {
|
|||
.polygon_mode = .fill, // How to handle filling points between vertices
|
||||
.line_width = 1.0, // How thick the lines should be when drawn
|
||||
.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_constant_factor = 0,
|
||||
.depth_bias_clamp = 0,
|
||||
|
@ -647,8 +726,11 @@ pub const VulkanRenderer = struct {
|
|||
.blend_constants = [_]f32{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
// -- Pipeline layout (TODO: Apply future descriptor set layouts) --
|
||||
const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{};
|
||||
// -- Pipeline layout --
|
||||
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);
|
||||
|
||||
|
@ -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 {
|
||||
// Information about how to begin each command
|
||||
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
|
||||
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
|
||||
command_buffer.drawIndexed(mesh.index_count, 1, 0, 0, 0);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue