|
| 1 | +# [zmath](https://github.com/michal-z/zig-gamedev/zmath) |
| 2 | + |
| 3 | +SIMD math library for game developers |
| 4 | + |
| 5 | +Tested on x86_64 and AArch64. |
| 6 | + |
| 7 | +Provides ~140 optimized routines and ~70 extensive tests. |
| 8 | + |
| 9 | +Can be used with any graphics API. |
| 10 | + |
| 11 | +Documentation can be found [here](https://github.com/michal-z/zig-gamedev/zmath/src/zmath.zig). |
| 12 | + |
| 13 | +Benchamrks can be found [here](https://github.com/michal-z/zig-gamedev/zmath/src/benchmark.zig). |
| 14 | + |
| 15 | +An intro article can be found [here](https://zig.news/michalz/fast-multi-platform-simd-math-library-in-zig-2adn). |
| 16 | + |
| 17 | +## Getting started |
| 18 | + |
| 19 | +Copy `zmath` into a subdirectory of your project and add the following to your `build.zig.zon` .dependencies: |
| 20 | +```zig |
| 21 | + .zmath = .{ .path = "libs/zmath" }, |
| 22 | +``` |
| 23 | + |
| 24 | +Then in your `build.zig` add: |
| 25 | + |
| 26 | +```zig |
| 27 | +pub fn build(b: *std.Build) void { |
| 28 | + const exe = b.addExecutable(.{ ... }); |
| 29 | +
|
| 30 | + const zmath = b.dependency("zmath", .{}); |
| 31 | + exe.root_module.addImport("zmath", zmath.module("root")); |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +Now in your code you may import and use zmath: |
| 36 | + |
| 37 | +```zig |
| 38 | +const zm = @import("zmath"); |
| 39 | +
|
| 40 | +pub fn main() !void { |
| 41 | + // |
| 42 | + // OpenGL/Vulkan example |
| 43 | + // |
| 44 | + const object_to_world = zm.rotationY(..); |
| 45 | + const world_to_view = zm.lookAtRh( |
| 46 | + zm.f32x4(3.0, 3.0, 3.0, 1.0), // eye position |
| 47 | + zm.f32x4(0.0, 0.0, 0.0, 1.0), // focus point |
| 48 | + zm.f32x4(0.0, 1.0, 0.0, 0.0), // up direction ('w' coord is zero because this is a vector not a point) |
| 49 | + ); |
| 50 | + // `perspectiveFovRhGl` produces Z values in [-1.0, 1.0] range (Vulkan app should use `perspectiveFovRh`) |
| 51 | + const view_to_clip = zm.perspectiveFovRhGl(0.25 * math.pi, aspect_ratio, 0.1, 20.0); |
| 52 | +
|
| 53 | + const object_to_view = zm.mul(object_to_world, world_to_view); |
| 54 | + const object_to_clip = zm.mul(object_to_view, view_to_clip); |
| 55 | +
|
| 56 | + // Transposition is needed because GLSL uses column-major matrices by default |
| 57 | + gl.uniformMatrix4fv(0, 1, gl.TRUE, zm.arrNPtr(&object_to_clip)); |
| 58 | + |
| 59 | + // In GLSL: gl_Position = vec4(in_position, 1.0) * object_to_clip; |
| 60 | + |
| 61 | + // |
| 62 | + // DirectX example |
| 63 | + // |
| 64 | + const object_to_world = zm.rotationY(..); |
| 65 | + const world_to_view = zm.lookAtLh( |
| 66 | + zm.f32x4(3.0, 3.0, -3.0, 1.0), // eye position |
| 67 | + zm.f32x4(0.0, 0.0, 0.0, 1.0), // focus point |
| 68 | + zm.f32x4(0.0, 1.0, 0.0, 0.0), // up direction ('w' coord is zero because this is a vector not a point) |
| 69 | + ); |
| 70 | + const view_to_clip = zm.perspectiveFovLh(0.25 * math.pi, aspect_ratio, 0.1, 20.0); |
| 71 | +
|
| 72 | + const object_to_view = zm.mul(object_to_world, world_to_view); |
| 73 | + const object_to_clip = zm.mul(object_to_view, view_to_clip); |
| 74 | + |
| 75 | + // Transposition is needed because HLSL uses column-major matrices by default |
| 76 | + const mem = allocateUploadMemory(...); |
| 77 | + zm.storeMat(mem, zm.transpose(object_to_clip)); |
| 78 | + |
| 79 | + // In HLSL: out_position_sv = mul(float4(in_position, 1.0), object_to_clip); |
| 80 | + |
| 81 | + // |
| 82 | + // 'WASD' camera movement example |
| 83 | + // |
| 84 | + { |
| 85 | + const speed = zm.f32x4s(10.0); |
| 86 | + const delta_time = zm.f32x4s(demo.frame_stats.delta_time); |
| 87 | + const transform = zm.mul(zm.rotationX(demo.camera.pitch), zm.rotationY(demo.camera.yaw)); |
| 88 | + var forward = zm.normalize3(zm.mul(zm.f32x4(0.0, 0.0, 1.0, 0.0), transform)); |
| 89 | +
|
| 90 | + zm.storeArr3(&demo.camera.forward, forward); |
| 91 | +
|
| 92 | + const right = speed * delta_time * zm.normalize3(zm.cross3(zm.f32x4(0.0, 1.0, 0.0, 0.0), forward)); |
| 93 | + forward = speed * delta_time * forward; |
| 94 | +
|
| 95 | + var cam_pos = zm.loadArr3(demo.camera.position); |
| 96 | +
|
| 97 | + if (keyDown('W')) { |
| 98 | + cam_pos += forward; |
| 99 | + } else if (keyDown('S')) { |
| 100 | + cam_pos -= forward; |
| 101 | + } |
| 102 | + if (keyDown('D')) { |
| 103 | + cam_pos += right; |
| 104 | + } else if (keyDown('A')) { |
| 105 | + cam_pos -= right; |
| 106 | + } |
| 107 | +
|
| 108 | + zm.storeArr3(&demo.camera.position, cam_pos); |
| 109 | + } |
| 110 | + |
| 111 | + // |
| 112 | + // SIMD wave equation solver example (works with vector width 4, 8 and 16) |
| 113 | + // 'T' can be F32x4, F32x8 or F32x16 |
| 114 | + // |
| 115 | + var z_index: i32 = 0; |
| 116 | + while (z_index < grid_size) : (z_index += 1) { |
| 117 | + const z = scale * @intToFloat(f32, z_index - grid_size / 2); |
| 118 | + const vz = zm.splat(T, z); |
| 119 | +
|
| 120 | + var x_index: i32 = 0; |
| 121 | + while (x_index < grid_size) : (x_index += zm.veclen(T)) { |
| 122 | + const x = scale * @intToFloat(f32, x_index - grid_size / 2); |
| 123 | + const vx = zm.splat(T, x) + voffset * zm.splat(T, scale); |
| 124 | +
|
| 125 | + const d = zm.sqrt(vx * vx + vz * vz); |
| 126 | + const vy = zm.sin(d - vtime); |
| 127 | +
|
| 128 | + const index = @intCast(usize, x_index + z_index * grid_size); |
| 129 | + zm.store(xslice[index..], vx, 0); |
| 130 | + zm.store(yslice[index..], vy, 0); |
| 131 | + zm.store(zslice[index..], vz, 0); |
| 132 | + } |
| 133 | + } |
| 134 | +} |
| 135 | +``` |
0 commit comments