├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.zig ├── libs ├── Camera.zig ├── Shader.zig ├── common.zig ├── gl.zig ├── mach-glfw ├── zmath │ ├── README.md │ ├── build.zig │ └── src │ │ ├── benchmark.zig │ │ ├── main.zig │ │ ├── util.zig │ │ └── zmath.zig └── zstbi │ ├── README.md │ ├── build.zig │ ├── libs │ └── stbi │ │ ├── stb_image.c │ │ ├── stb_image.h │ │ ├── stb_image_resize.h │ │ └── stb_image_write.h │ └── src │ └── zstbi.zig └── src └── getting_started ├── basic_lighting ├── build.zig ├── content │ ├── awesomeface.png │ ├── container.jpg │ ├── light_shader.fs │ ├── light_shader.vs │ ├── shader.fs │ └── shader.vs ├── image.png └── main.zig ├── camera_rotate ├── build.zig ├── content │ ├── awesomeface.png │ ├── container.jpg │ ├── shader.fs │ └── shader.vs └── main.zig ├── coordinate_systems ├── build.zig ├── content │ ├── awesomeface.png │ ├── container.jpg │ ├── shader.fs │ └── shader.vs ├── image.png └── main.zig ├── hello_rectangle ├── README.md ├── build.zig ├── image.png └── main.zig ├── hello_triangle ├── README.md ├── build.zig ├── excercise_1.png ├── exercise_2.png ├── exercise_3.png ├── image.png └── main.zig ├── shaders ├── build.zig ├── image.png ├── main.zig └── shaders │ ├── shader.fs │ ├── shader.vs │ ├── shader_ex3.fs │ └── shader_ex3.vs ├── simple_camera ├── build.zig ├── content │ ├── awesomeface.png │ ├── container.jpg │ ├── shader.fs │ └── shader.vs └── main.zig ├── textures ├── build.zig ├── content │ ├── awesomeface.png │ ├── container.jpg │ ├── shader.fs │ ├── shader.vs │ └── wall.jpg ├── image.png └── main.zig └── transformations ├── build.zig ├── content ├── awesomeface.png ├── container.jpg ├── shader.fs ├── shader.vs └── wall.jpg ├── image.png └── main.zig /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is for zig-specific build artifacts. 2 | # If you have OS-specific or editor-specific files to ignore, 3 | # such as *.swp or .DS_Store, put those in your global 4 | # ~/.gitignore and put this in your ~/.gitconfig: 5 | # 6 | # [core] 7 | # excludesfile = ~/.gitignore 8 | # 9 | # Cheers! 10 | # -andrewrk 11 | 12 | zig-cache/ 13 | zig-out/ 14 | /release/ 15 | /debug/ 16 | /build/ 17 | /build-*/ 18 | /docgen_tmp/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/mach-glfw"] 2 | path = libs/mach-glfw 3 | url = https://github.com/craftlinks/mach-glfw.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Files in a directory with a separate LICENSE file may contain files under different license terms, 2 | described within that LICENSE file. 3 | 4 | All other files are licensed under the Apache License, Version 2.0 (see LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 5 | or the MIT license (see LICENSE-MIT or http://opensource.org/licenses/MIT), at your option. 6 | 7 | All files in the project without exclusions may not be copied, modified, or distributed except 8 | according to the terms above. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [**Learn OpenGL**](https://learnopengl.com/) using [Zig](https://ziglang.org/). 2 | 3 | Windowing library: [mach/glfw - Ziggified GLFW bindings](https://github.com/hexops/mach-glfw) by slimsag 4 | (Stephen Gutekanst). 5 | 6 | OpenGL bindings for Zig were generated by the [zig-opengl](https://github.com/MasterQ32/zig-opengl) project by MasterQ32 (Felix Queißner). 7 | 8 | [zstbi](https://github.com/michal-z/zig-gamedev/tree/main/libs/zstbi) - stb image bindings, [zmath](https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath) - SIMD math library, provided by [Michal Ziulek](https://github.com/michal-z), part of the [zig-gamedev](https://github.com/michal-z/zig-gamedev) project. 9 | 10 | Sample programs can be used together with the reference book: [Learn OpenGL - Graphics Programming](https://learnopengl.com/) by [Joey de Vries](http://joeydevries.com/#home). 11 | 12 | --- 13 | Zig Language installation [How-to instructions](https://ziglang.org/learn/getting-started/). 14 | 15 | --- 16 | ## **I. Getting Started** 17 | ### Hello Triangle 18 | 19 | - [**hello_triangle**](src/getting_started/hello_triangle/): Minimal setup for drawing a trianlge on screen.
`zig build hello_triangle-run` 20 |
hello triangle 21 | - [**hello_rectangle**](src/getting_started/hello_rectangle/): Draw a rectangle efficiently with indexed rendering using the **'Element Buffer Object'**.
`zig build hello_rectangle-run` 22 |
hello triangle 23 | 24 | ### Shaders 25 | - [**shaders**](src/getting_started/shaders/): Little programs that rest on the GPU
26 | `zig build shaders-run` 27 |
shaders 28 | 29 | [Shader](src/common/shader.zig) struct mirrors the C++ Shader Class in the book. 30 | 31 | ### Textures 32 | - [**textures**](src/getting_started/textures/): Decorate objects with textures
33 | `zig build textures-run` 34 |
textures 35 | 36 | ### Transformations 37 | - [**Transformations**](src/getting_started/transformations/): Apply a transformation matrix to vertex data
38 | `zig build transformations-run` 39 |
transformations 40 | 41 | ### Coordinate Systems 42 | - [**Coordinate systems**](src/getting_started/coordinate_systems/): Model, View, Projection matrices
43 | `zig build coordinate_systems-run` 44 |
coordinate_systems 45 | 46 | ### Camera 47 | - [**Camera rotation**](src/getting_started/camera_rotate/): Camera rotation around world origin
48 | `zig build camera_rotate-run` 49 | 50 | - [**Simple camera**](src/getting_started/simple_camera/): Fly-like camera
51 | `zig build simple_camera-run` 52 | 53 | ## **II. Lighting** 54 | 55 | ### Basic Lighting 56 | - [**Basic Lighting**](src/getting_started/basic_lighting/): Phong lighting model
57 | `zig build basic_lighting-run` 58 |
basic_lighting -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const glfw = @import("libs/mach-glfw/build.zig"); 3 | const zstbi = @import("libs/zstbi/build.zig"); 4 | const zmath = @import("libs/zmath/build.zig"); 5 | 6 | fn installExe(b: *std.build.Builder, exe: *std.build.LibExeObjStep, comptime name: []const u8, dependencies: []const ExeDependency) !void { 7 | exe.want_lto = false; 8 | if (exe.optimize == .ReleaseFast) 9 | exe.strip = true; 10 | 11 | for (dependencies) |dep| { 12 | exe.addModule(dep.name, dep.module); 13 | } 14 | 15 | try glfw.link(b, exe, .{}); 16 | zstbi.link(exe); 17 | 18 | const install = b.step(name, "Build '" ++ name); 19 | install.dependOn(&b.addInstallArtifact(exe).step); 20 | 21 | const run_step = b.step(name ++ "-run", "Run " ++ name); 22 | const run_cmd = exe.run(); 23 | run_cmd.step.dependOn(install); 24 | run_step.dependOn(&run_cmd.step); 25 | 26 | b.getInstallStep().dependOn(install); 27 | } 28 | 29 | pub fn build(b: *std.Build) !void { 30 | const options = Options{ 31 | .build_mode = b.standardOptimizeOption(.{}), 32 | .target = b.standardTargetOptions(.{}), 33 | }; 34 | 35 | // Modules 36 | const zmath_module = zmath.package(b, .{}).module; 37 | const zstbi_module = zstbi.package(b, .{}).module; 38 | const glfw_module = glfw.module(b); 39 | const gl_module = b.createModule(.{ .source_file = .{ .path = "libs/gl.zig" }, .dependencies = &.{} }); 40 | const shader_module = b.createModule(.{ .source_file = .{ .path = "libs/Shader.zig" }, .dependencies = &.{.{ .name = "gl", .module = gl_module }} }); 41 | const common_module = b.createModule(.{ .source_file = .{ .path = "libs/common.zig" }, .dependencies = &.{} }); 42 | const camera_module = b.createModule(.{ .source_file = .{ .path = "libs/Camera.zig" }, .dependencies = &.{ .{ .name = "gl", .module = gl_module }, .{ .name = "Shader", .module = shader_module }, .{ .name = "zmath", .module = zmath_module }, .{ .name = "common", .module = common_module } } }); 43 | 44 | // Dependencies 45 | const exe_dependencies: []const ExeDependency = &.{ .{ .name = "zmath", .module = zmath_module }, .{ .name = "zstbi", .module = zstbi_module }, .{ .name = "gl", .module = gl_module }, .{ .name = "glfw", .module = glfw_module }, .{ .name = "Shader", .module = shader_module }, .{ .name = "common", .module = common_module }, .{ .name = "Camera", .module = camera_module } }; 46 | 47 | const hello_triangle = @import("src/getting_started/hello_triangle/build.zig"); 48 | const hello_rectangle = @import("src/getting_started/hello_rectangle/build.zig"); 49 | const shaders = @import("src/getting_started/shaders/build.zig"); 50 | const textures = @import("src/getting_started/textures/build.zig"); 51 | const transformations = @import("src/getting_started/transformations/build.zig"); 52 | const coordinate_systems = @import("src/getting_started/coordinate_systems/build.zig"); 53 | const camera_rotate = @import("src/getting_started/camera_rotate/build.zig"); 54 | const simple_camera = @import("src/getting_started/simple_camera/build.zig"); 55 | const basic_lighting = @import("src/getting_started/basic_lighting/build.zig"); 56 | 57 | try installExe(b, hello_triangle.build(b, options), "hello_triangle", exe_dependencies); 58 | try installExe(b, hello_rectangle.build(b, options), "hello_rectangle", exe_dependencies); 59 | try installExe(b, shaders.build(b, options), "shaders", exe_dependencies); 60 | try installExe(b, textures.build(b, options), "textures", exe_dependencies); 61 | try installExe(b, transformations.build(b, options), "transformations", exe_dependencies); 62 | try installExe(b, coordinate_systems.build(b, options), "coordinate_systems", exe_dependencies); 63 | try installExe(b, camera_rotate.build(b, options), "camera_rotate", exe_dependencies); 64 | try installExe(b, simple_camera.build(b, options), "simple_camera", exe_dependencies); 65 | try installExe(b, basic_lighting.build(b, options), "basic_lighting", exe_dependencies); 66 | } 67 | 68 | pub const Options = struct { 69 | build_mode: std.builtin.Mode, 70 | target: std.zig.CrossTarget, 71 | }; 72 | 73 | pub const ExeDependency = struct { 74 | name: []const u8, 75 | module: *std.Build.Module, 76 | }; 77 | -------------------------------------------------------------------------------- /libs/Camera.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const gl = @import("gl"); 3 | const zm = @import("zmath"); 4 | const common = @import("common"); 5 | const Camera = @This(); 6 | 7 | pub const CameraMovement = enum { 8 | FORWARD, 9 | BACKWARD, 10 | LEFT, 11 | RIGHT, 12 | }; 13 | 14 | const WORLD_UP = zm.loadArr3(.{0.0, 1.0, 0.0}); 15 | const MOVEMENT_SPEED: f32 = 2.5; 16 | const MOUSE_SENSITIVITY: f32 = 0.1; 17 | 18 | // Camera attributes 19 | position: zm.F32x4 = zm.loadArr3(.{0.0, 0.0, 0.0}), 20 | front: zm.F32x4 = zm.loadArr3(.{0.0, 0.0, -1.0}), 21 | up: zm.F32x4 = undefined, 22 | right: zm.F32x4 = undefined, 23 | 24 | // euler Angles 25 | yaw: f32 = -90, 26 | pitch: f32 = 0.0, 27 | 28 | // camera options 29 | zoom: f32 = 45.0, 30 | 31 | 32 | pub fn camera(position: ?zm.F32x4) Camera { 33 | const _position = p: { 34 | if(position) |value| { 35 | break :p value; 36 | } 37 | else { 38 | break :p zm.loadArr3(.{0.0, 0.0, 0.0}); 39 | } 40 | }; 41 | 42 | const _front = zm.loadArr3(.{0.0, 0.0, -1.0}); 43 | const _world_up = zm.loadArr3(.{0.0, 1.0, 0.0}); 44 | const _right = zm.normalize3(zm.cross3(_front, _world_up)); 45 | const _up = zm.normalize3(zm.cross3(_right, _front)); 46 | 47 | return Camera{ 48 | .position = _position, 49 | .right = _right, // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. 50 | .up = _up, 51 | }; 52 | } 53 | 54 | // returns the view matrix calculated using Euler Angles and the LookAt Matrix 55 | pub fn getViewMatrix(self: *Camera) zm.Mat { 56 | return zm.lookAtRh(self.position, self.position + self.front, self.up); 57 | } 58 | 59 | // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) 60 | pub fn processKeyboard(self: *Camera, direction: Camera.CameraMovement, delta_time: f32) void { 61 | const velocity = zm.f32x4s(MOVEMENT_SPEED * delta_time); 62 | switch (direction) { 63 | .FORWARD => self.position += self.front * velocity, 64 | .BACKWARD => self.position -= self.front * velocity, 65 | .LEFT => self.position -= self.right * velocity, 66 | .RIGHT => self.position += self.right * velocity, 67 | } 68 | } 69 | 70 | // processes input received from a mouse input system. Expects the offset value in both the x and y direction. 71 | pub fn processMouseMovement(self: *Camera, xoffset: f64, yoffset: f64, constrain_pitch: bool) void { 72 | const _xoffset = @floatCast(f32,xoffset) * MOUSE_SENSITIVITY; 73 | const _yoffset = @floatCast(f32, yoffset) * MOUSE_SENSITIVITY; 74 | 75 | self.yaw += _xoffset; 76 | self.pitch += _yoffset; 77 | 78 | // make sure that when pitch is out of bounds, screen doesn't get flipped 79 | if (constrain_pitch) { 80 | if (self.pitch > 89.0) 81 | self.pitch = 89.0; 82 | if (self.pitch < -89.0) 83 | self.pitch = -89.0; 84 | } 85 | 86 | // update Front, Right and Up Vectors using the updated Euler angles 87 | self.updateCameraVectors(); 88 | } 89 | 90 | // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis 91 | pub fn processMouseScroll(self: *Camera, yoffset: f64) void { 92 | self.zoom -= @floatCast(f32,yoffset); 93 | if (self.zoom < 1.0) 94 | self.zoom = 1.0; 95 | if (self.zoom > 45.0) 96 | self.zoom = 45.0; 97 | } 98 | 99 | // calculates the front vector from the Camera's (updated) Euler Angles 100 | fn updateCameraVectors(self: *Camera) void { 101 | // calculate the new Front vector 102 | var front: zm.F32x4 = undefined; 103 | front[0] = @cos(self.yaw * common.RAD_CONVERSION) * @cos(self.pitch * common.RAD_CONVERSION); 104 | front[1] = @sin(self.pitch * common.RAD_CONVERSION); 105 | front[2] = @sin(self.yaw * common.RAD_CONVERSION) * @cos(self.pitch * common.RAD_CONVERSION); 106 | self.front = front; 107 | // also re-calculate the Right and Up vector 108 | self.right = zm.normalize3(zm.cross3(self.front, WORLD_UP)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. 109 | self.up = zm.normalize3(zm.cross3(self.right, self.front)); 110 | } 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /libs/Shader.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const gl = @import("gl"); 3 | const Shader = @This(); 4 | 5 | // The program ID 6 | ID: c_uint, 7 | 8 | pub fn create(arena: std.mem.Allocator, vs_path:[]const u8, fs_path:[]const u8) Shader { 9 | 10 | // Create vertex shader 11 | var vertexShader: c_uint = undefined; 12 | vertexShader = gl.createShader(gl.VERTEX_SHADER); 13 | defer gl.deleteShader(vertexShader); 14 | const full_vs_path = std.fs.path.join(arena, &.{ 15 | std.fs.selfExeDirPathAlloc(arena) catch unreachable, 16 | vs_path, 17 | }) catch unreachable; 18 | 19 | const full_fs_path = std.fs.path.join(arena, &.{ 20 | std.fs.selfExeDirPathAlloc(arena) catch unreachable, 21 | fs_path, 22 | }) catch unreachable; 23 | 24 | const vs_file = std.fs.openFileAbsolute(full_vs_path, .{}) catch unreachable; 25 | const vs_code = vs_file.readToEndAllocOptions(arena, (10 * 1024), null, @alignOf(u8), 0) catch unreachable; 26 | 27 | const fs_file = std.fs.openFileAbsolute(full_fs_path, .{}) catch unreachable; 28 | const fs_code = fs_file.readToEndAllocOptions(arena, (10 * 1024), null, @alignOf(u8), 0) catch unreachable; 29 | 30 | // Attach the shader source to the vertex shader object and compile it 31 | gl.shaderSource(vertexShader, 1, @ptrCast([*c]const [*c]const u8, &vs_code), 0); 32 | gl.compileShader(vertexShader); 33 | 34 | // Check if vertex shader was compiled successfully 35 | var success: c_int = undefined; 36 | var infoLog: [512]u8 = [_]u8{0} ** 512; 37 | 38 | gl.getShaderiv(vertexShader, gl.COMPILE_STATUS, &success); 39 | 40 | if (success == 0) { 41 | gl.getShaderInfoLog(vertexShader, 512, 0, &infoLog); 42 | std.log.err("{s}", .{infoLog}); 43 | } 44 | 45 | // Fragment shader 46 | var fragmentShader: c_uint = undefined; 47 | fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 48 | defer gl.deleteShader(fragmentShader); 49 | 50 | gl.shaderSource(fragmentShader, 1, @ptrCast([*c]const [*c]const u8, &fs_code), 0); 51 | gl.compileShader(fragmentShader); 52 | 53 | gl.getShaderiv(fragmentShader, gl.COMPILE_STATUS, &success); 54 | 55 | if (success == 0) { 56 | gl.getShaderInfoLog(fragmentShader, 512, 0, &infoLog); 57 | std.log.err("{s}", .{infoLog}); 58 | } 59 | 60 | // create a program object 61 | const shaderProgram = gl.createProgram(); 62 | 63 | // attach compiled shader objects to the program object and link 64 | gl.attachShader(shaderProgram, vertexShader); 65 | gl.attachShader(shaderProgram, fragmentShader); 66 | gl.linkProgram(shaderProgram); 67 | 68 | // check if shader linking was successfull 69 | gl.getProgramiv(shaderProgram, gl.LINK_STATUS, &success); 70 | if (success == 0) { 71 | gl.getProgramInfoLog(shaderProgram, 512, 0, &infoLog); 72 | std.log.err("{s}", .{infoLog}); 73 | } 74 | return Shader{.ID = shaderProgram}; 75 | } 76 | 77 | pub fn use(self: Shader) void { 78 | gl.useProgram(self.ID); 79 | } 80 | 81 | pub fn setBool(self: Shader, name: [*c]const u8, value: bool) void { 82 | gl.uniform1i(gl.getUniformLocation(self.ID, name), @boolToInt(value)); 83 | } 84 | 85 | pub fn setInt(self: Shader, name: [*c]const u8, value: u32) void { 86 | gl.uniform1i(gl.getUniformLocation(self.ID, name), @intCast(c_int,value)); 87 | } 88 | 89 | pub fn setFloat(self: Shader, name: [*c]const u8, value: f32) void { 90 | gl.uniform1f(gl.getUniformLocation(self.ID, name), value); 91 | } 92 | 93 | pub fn setVec3f(self: Shader, name: [*c]const u8, value: [3]f32) void { 94 | gl.uniform3f(gl.getUniformLocation(self.ID, name), value[0], value[1], value[2]); 95 | } 96 | 97 | pub fn setMat4f(self: Shader, name: [*c]const u8, value: [16]f32) void { 98 | const matLoc = gl.getUniformLocation(self.ID, name); 99 | gl.uniformMatrix4fv(matLoc, 1, gl.FALSE, &value); 100 | } -------------------------------------------------------------------------------- /libs/common.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | 4 | pub fn pathToContent(arena: std.mem.Allocator, resource_relative_path: [:0]const u8) ![4096:0] u8 { 5 | const exe_path = std.fs.selfExeDirPathAlloc(arena) catch unreachable; 6 | const content_path = std.fs.path.join(arena, &.{exe_path, resource_relative_path}) catch unreachable; 7 | var content_path_zero : [4096:0]u8 = undefined; 8 | if (content_path.len >= 4096) return error.NameTooLong; 9 | std.mem.copy(u8, &content_path_zero, content_path); 10 | content_path_zero[content_path.len] = 0; 11 | return content_path_zero; 12 | } 13 | 14 | // Create the transformation matrices: 15 | // Degree to radians conversion factor 16 | pub const RAD_CONVERSION = math.pi / 180.0; -------------------------------------------------------------------------------- /libs/mach-glfw: -------------------------------------------------------------------------------- 1 | /home/hugh/GitRepos/Zig/Snow/mach-glfw -------------------------------------------------------------------------------- /libs/zmath/README.md: -------------------------------------------------------------------------------- 1 | # zmath v0.9.5 - SIMD math library for game developers 2 | 3 | Tested on x86_64 and AArch64. 4 | 5 | Provides ~140 optimized routines and ~70 extensive tests. 6 | 7 | Can be used with any graphics API. 8 | 9 | Documentation can be found [here](https://github.com/michal-z/zig-gamedev/blob/main/libs/zmath/src/zmath.zig). 10 | 11 | Benchamrks can be found [here](https://github.com/michal-z/zig-gamedev/blob/main/libs/zmath/src/benchmark.zig). 12 | 13 | An intro article can be found [here](https://zig.news/michalz/fast-multi-platform-simd-math-library-in-zig-2adn). 14 | 15 | ## Getting started 16 | 17 | Copy `zmath` folder to a `libs` subdirectory of the root of your project. 18 | 19 | Then in your `build.zig` add: 20 | 21 | ```zig 22 | const std = @import("std"); 23 | const zmath = @import("libs/zmath/build.zig"); 24 | 25 | pub fn build(b: *std.Build) void { 26 | ... 27 | const zmath_pkg = zmath.package(b, .{}); 28 | 29 | exe.addModule("zmath", zmath_pkg.module); 30 | } 31 | ``` 32 | 33 | Now in your code you may import and use zmath: 34 | 35 | ```zig 36 | const zm = @import("zmath"); 37 | 38 | pub fn main() !void { 39 | // 40 | // OpenGL/Vulkan example 41 | // 42 | const object_to_world = zm.rotationY(..); 43 | const world_to_view = zm.lookAtRh( 44 | zm.f32x4(3.0, 3.0, 3.0, 1.0), // eye position 45 | zm.f32x4(0.0, 0.0, 0.0, 1.0), // focus point 46 | zm.f32x4(0.0, 1.0, 0.0, 0.0), // up direction ('w' coord is zero because this is a vector not a point) 47 | ); 48 | // `perspectiveFovRhGl` produces Z values in [-1.0, 1.0] range (Vulkan app should use `perspectiveFovRh`) 49 | const view_to_clip = zm.perspectiveFovRhGl(0.25 * math.pi, aspect_ratio, 0.1, 20.0); 50 | 51 | const object_to_view = zm.mul(object_to_world, world_to_view); 52 | const object_to_clip = zm.mul(object_to_view, view_to_clip); 53 | 54 | // Transposition is needed because GLSL uses column-major matrices by default 55 | gl.uniformMatrix4fv(0, 1, gl.TRUE, zm.arrNPtr(&object_to_clip)); 56 | 57 | // In GLSL: gl_Position = vec4(in_position, 1.0) * object_to_clip; 58 | 59 | // 60 | // DirectX example 61 | // 62 | const object_to_world = zm.rotationY(..); 63 | const world_to_view = zm.lookAtLh( 64 | zm.f32x4(3.0, 3.0, -3.0, 1.0), // eye position 65 | zm.f32x4(0.0, 0.0, 0.0, 1.0), // focus point 66 | zm.f32x4(0.0, 1.0, 0.0, 0.0), // up direction ('w' coord is zero because this is a vector not a point) 67 | ); 68 | const view_to_clip = zm.perspectiveFovLh(0.25 * math.pi, aspect_ratio, 0.1, 20.0); 69 | 70 | const object_to_view = zm.mul(object_to_world, world_to_view); 71 | const object_to_clip = zm.mul(object_to_view, view_to_clip); 72 | 73 | // Transposition is needed because HLSL uses column-major matrices by default 74 | const mem = allocateUploadMemory(...); 75 | zm.storeMat(mem, zm.transpose(object_to_clip)); 76 | 77 | // In HLSL: out_position_sv = mul(float4(in_position, 1.0), object_to_clip); 78 | 79 | // 80 | // 'WASD' camera movement example 81 | // 82 | { 83 | const speed = zm.f32x4s(10.0); 84 | const delta_time = zm.f32x4s(demo.frame_stats.delta_time); 85 | const transform = zm.mul(zm.rotationX(demo.camera.pitch), zm.rotationY(demo.camera.yaw)); 86 | var forward = zm.normalize3(zm.mul(zm.f32x4(0.0, 0.0, 1.0, 0.0), transform)); 87 | 88 | zm.storeArr3(&demo.camera.forward, forward); 89 | 90 | const right = speed * delta_time * zm.normalize3(zm.cross3(zm.f32x4(0.0, 1.0, 0.0, 0.0), forward)); 91 | forward = speed * delta_time * forward; 92 | 93 | var cam_pos = zm.loadArr3(demo.camera.position); 94 | 95 | if (keyDown('W')) { 96 | cam_pos += forward; 97 | } else if (keyDown('S')) { 98 | cam_pos -= forward; 99 | } 100 | if (keyDown('D')) { 101 | cam_pos += right; 102 | } else if (keyDown('A')) { 103 | cam_pos -= right; 104 | } 105 | 106 | zm.storeArr3(&demo.camera.position, cam_pos); 107 | } 108 | 109 | // 110 | // SIMD wave equation solver example (works with vector width 4, 8 and 16) 111 | // 'T' can be F32x4, F32x8 or F32x16 112 | // 113 | var z_index: i32 = 0; 114 | while (z_index < grid_size) : (z_index += 1) { 115 | const z = scale * @intToFloat(f32, z_index - grid_size / 2); 116 | const vz = zm.splat(T, z); 117 | 118 | var x_index: i32 = 0; 119 | while (x_index < grid_size) : (x_index += zm.veclen(T)) { 120 | const x = scale * @intToFloat(f32, x_index - grid_size / 2); 121 | const vx = zm.splat(T, x) + voffset * zm.splat(T, scale); 122 | 123 | const d = zm.sqrt(vx * vx + vz * vz); 124 | const vy = zm.sin(d - vtime); 125 | 126 | const index = @intCast(usize, x_index + z_index * grid_size); 127 | zm.store(xslice[index..], vx, 0); 128 | zm.store(yslice[index..], vy, 0); 129 | zm.store(zslice[index..], vz, 0); 130 | } 131 | } 132 | } 133 | ``` 134 | -------------------------------------------------------------------------------- /libs/zmath/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Options = struct { 4 | prefer_determinism: bool = false, 5 | }; 6 | 7 | pub const Package = struct { 8 | module: *std.Build.Module, 9 | options: Options, 10 | options_module: *std.Build.Module, 11 | }; 12 | 13 | pub fn package( 14 | b: *std.Build, 15 | args: struct { 16 | options: Options = .{}, 17 | }, 18 | ) Package { 19 | const step = b.addOptions(); 20 | step.addOption(bool, "prefer_determinism", args.options.prefer_determinism); 21 | 22 | const options_module = step.createModule(); 23 | 24 | const module = b.createModule(.{ 25 | .source_file = .{ .path = thisDir() ++ "/src/main.zig" }, 26 | .dependencies = &.{ 27 | .{ .name = "zmath_options", .module = options_module }, 28 | }, 29 | }); 30 | 31 | return .{ 32 | .module = module, 33 | .options = args.options, 34 | .options_module = options_module, 35 | }; 36 | } 37 | 38 | pub fn build(b: *std.Build) void { 39 | const build_mode = b.standardOptimizeOption(.{}); 40 | const target = b.standardTargetOptions(.{}); 41 | const tests = buildTests(b, build_mode, target); 42 | 43 | const test_step = b.step("test", "Run zmath tests"); 44 | test_step.dependOn(&tests.step); 45 | } 46 | 47 | pub fn buildTests( 48 | b: *std.Build, 49 | build_mode: std.builtin.Mode, 50 | target: std.zig.CrossTarget, 51 | ) *std.Build.CompileStep { 52 | const tests = b.addTest(.{ 53 | .root_source_file = .{ .path = thisDir() ++ "/src/main.zig" }, 54 | .target = target, 55 | .optimize = build_mode, 56 | }); 57 | const zmath_pkg = package(b, .{}); 58 | tests.addModule("zmath_options", zmath_pkg.options_module); 59 | return tests; 60 | } 61 | 62 | pub fn buildBenchmarks( 63 | b: *std.Build, 64 | target: std.zig.CrossTarget, 65 | ) *std.Build.CompileStep { 66 | const exe = b.addExecutable(.{ 67 | .name = "benchmark", 68 | .root_source_file = .{ .path = thisDir() ++ "/src/benchmark.zig" }, 69 | .target = target, 70 | .optimize = .ReleaseFast, 71 | }); 72 | const zmath_pkg = package(b, .{}); 73 | exe.addModule("zmath", zmath_pkg.module); 74 | return exe; 75 | } 76 | 77 | inline fn thisDir() []const u8 { 78 | return comptime std.fs.path.dirname(@src().file) orelse "."; 79 | } 80 | -------------------------------------------------------------------------------- /libs/zmath/src/benchmark.zig: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // zmath - benchmarks 3 | // ------------------------------------------------------------------------------------------------- 4 | // 'zig build benchmark' in the root project directory will build and run 'ReleaseFast' configuration. 5 | // 6 | // ------------------------------------------------------------------------------------------------- 7 | // 'AMD Ryzen 9 3950X 16-Core Processor', Windows 11, Zig 0.10.0-dev.2620+0e9458a3f 8 | // ------------------------------------------------------------------------------------------------- 9 | // matrix mul benchmark (AOS) - scalar version: 1.5880s, zmath version: 1.0642s 10 | // cross3, scale, bias benchmark (AOS) - scalar version: 0.9318s, zmath version: 0.6888s 11 | // cross3, dot3, scale, bias benchmark (AOS) - scalar version: 1.2258s, zmath version: 1.1095s 12 | // quaternion mul benchmark (AOS) - scalar version: 1.4123s, zmath version: 0.6958s 13 | // wave benchmark (SOA) - scalar version: 4.8165s, zmath version: 0.7338s 14 | // 15 | // ------------------------------------------------------------------------------------------------- 16 | // 'AMD Ryzen 7 5800X 8-Core Processer', Linux 5.17.14, Zig 0.10.0-dev.2624+d506275a0 17 | // ------------------------------------------------------------------------------------------------- 18 | // matrix mul benchmark (AOS) - scalar version: 1.3672s, zmath version: 0.8617s 19 | // cross3, scale, bias benchmark (AOS) - scalar version: 0.6586s, zmath version: 0.4803s 20 | // cross3, dot3, scale, bias benchmark (AOS) - scalar version: 1.0620s, zmath version: 0.8942s 21 | // quaternion mul benchmark (AOS) - scalar version: 1.1324s, zmath version: 0.6064s 22 | // wave benchmark (SOA) - scalar version: 3.6598s, zmath version: 0.4231s 23 | // 24 | // ------------------------------------------------------------------------------------------------- 25 | // 'Apple M1 Max', macOS Version 12.4, Zig 0.10.0-dev.2657+74442f350 26 | // ------------------------------------------------------------------------------------------------- 27 | // matrix mul benchmark (AOS) - scalar version: 1.0297s, zmath version: 1.0538s 28 | // cross3, scale, bias benchmark (AOS) - scalar version: 0.6294s, zmath version: 0.6532s 29 | // cross3, dot3, scale, bias benchmark (AOS) - scalar version: 0.9807s, zmath version: 1.0988s 30 | // quaternion mul benchmark (AOS) - scalar version: 1.5413s, zmath version: 0.7800s 31 | // wave benchmark (SOA) - scalar version: 3.4220s, zmath version: 1.0255s 32 | // 33 | // ------------------------------------------------------------------------------------------------- 34 | // '11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz', Windows 11, Zig 0.10.0-dev.2620+0e9458a3f 35 | // ------------------------------------------------------------------------------------------------- 36 | // matrix mul benchmark (AOS) - scalar version: 2.2308s, zmath version: 0.9376s 37 | // cross3, scale, bias benchmark (AOS) - scalar version: 1.0821s, zmath version: 0.5110s 38 | // cross3, dot3, scale, bias benchmark (AOS) - scalar version: 1.6580s, zmath version: 0.9167s 39 | // quaternion mul benchmark (AOS) - scalar version: 2.0139s, zmath version: 0.5856s 40 | // wave benchmark (SOA) - scalar version: 3.7832s, zmath version: 0.3642s 41 | // 42 | // ------------------------------------------------------------------------------------------------- 43 | 44 | pub fn main() !void { 45 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 46 | defer _ = gpa.deinit(); 47 | const allocator = gpa.allocator(); 48 | 49 | // m = mul(ma, mb); data set fits in L1 cache; AOS data layout. 50 | try mat4MulBenchmark(allocator, 100_000); 51 | 52 | // v = 0.01 * cross3(va, vb) + vec3(1.0); data set fits in L1 cache; AOS data layout. 53 | try cross3ScaleBiasBenchmark(allocator, 10_000); 54 | 55 | // v = dot3(va, vb) * (0.1 * cross3(va, vb) + vec3(1.0)); data set fits in L1 cache; AOS data layout. 56 | try cross3Dot3ScaleBiasBenchmark(allocator, 10_000); 57 | 58 | // q = qmul(qa, qb); data set fits in L1 cache; AOS data layout. 59 | try quatBenchmark(allocator, 10_000); 60 | 61 | // d = sqrt(x * x + z * z); y = sin(d - t); SOA layout. 62 | try waveBenchmark(allocator, 1_000); 63 | } 64 | 65 | const std = @import("std"); 66 | const time = std.time; 67 | const Timer = time.Timer; 68 | const zm = @import("zmath"); 69 | 70 | var prng = std.rand.DefaultPrng.init(0); 71 | const random = prng.random(); 72 | 73 | noinline fn mat4MulBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void { 74 | std.debug.print("{s:>42} - ", .{"matrix mul benchmark (AOS)"}); 75 | 76 | var data0 = std.ArrayList([16]f32).init(allocator); 77 | defer data0.deinit(); 78 | var data1 = std.ArrayList([16]f32).init(allocator); 79 | defer data1.deinit(); 80 | 81 | var i: usize = 0; 82 | while (i < 64) : (i += 1) { 83 | try data0.append([16]f32{ 84 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 85 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 86 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 87 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 88 | }); 89 | try data1.append([16]f32{ 90 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 91 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 92 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 93 | random.float(f32), random.float(f32), random.float(f32), random.float(f32), 94 | }); 95 | } 96 | 97 | // Warmup, fills L1 cache. 98 | i = 0; 99 | while (i < 100) : (i += 1) { 100 | for (data1.items) |b| { 101 | for (data0.items) |a| { 102 | const ma = zm.loadMat(a[0..]); 103 | const mb = zm.loadMat(b[0..]); 104 | const r = zm.mul(ma, mb); 105 | std.mem.doNotOptimizeAway(&r); 106 | } 107 | } 108 | } 109 | 110 | { 111 | i = 0; 112 | var timer = try Timer.start(); 113 | const start = timer.lap(); 114 | while (i < count) : (i += 1) { 115 | for (data1.items) |b| { 116 | for (data0.items) |a| { 117 | const r = [16]f32{ 118 | a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12], 119 | a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13], 120 | a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14], 121 | a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15], 122 | a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12], 123 | a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13], 124 | a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14], 125 | a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15], 126 | a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12], 127 | a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13], 128 | a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14], 129 | a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15], 130 | a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12], 131 | a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13], 132 | a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14], 133 | a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15], 134 | }; 135 | std.mem.doNotOptimizeAway(&r); 136 | } 137 | } 138 | } 139 | const end = timer.read(); 140 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 141 | 142 | std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s}); 143 | } 144 | 145 | { 146 | i = 0; 147 | var timer = try Timer.start(); 148 | const start = timer.lap(); 149 | while (i < count) : (i += 1) { 150 | for (data1.items) |b| { 151 | for (data0.items) |a| { 152 | const ma = zm.loadMat(a[0..]); 153 | const mb = zm.loadMat(b[0..]); 154 | const r = zm.mul(ma, mb); 155 | std.mem.doNotOptimizeAway(&r); 156 | } 157 | } 158 | } 159 | const end = timer.read(); 160 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 161 | 162 | std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s}); 163 | } 164 | } 165 | 166 | noinline fn cross3ScaleBiasBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void { 167 | std.debug.print("{s:>42} - ", .{"cross3, scale, bias benchmark (AOS)"}); 168 | 169 | var data0 = std.ArrayList([3]f32).init(allocator); 170 | defer data0.deinit(); 171 | var data1 = std.ArrayList([3]f32).init(allocator); 172 | defer data1.deinit(); 173 | 174 | var i: usize = 0; 175 | while (i < 256) : (i += 1) { 176 | try data0.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) }); 177 | try data1.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) }); 178 | } 179 | 180 | // Warmup, fills L1 cache. 181 | i = 0; 182 | while (i < 100) : (i += 1) { 183 | for (data1.items) |b| { 184 | for (data0.items) |a| { 185 | const va = zm.loadArr3(a); 186 | const vb = zm.loadArr3(b); 187 | const cp = zm.f32x4s(0.01) * zm.cross3(va, vb) + zm.f32x4s(1.0); 188 | std.mem.doNotOptimizeAway(&cp); 189 | } 190 | } 191 | } 192 | 193 | { 194 | i = 0; 195 | var timer = try Timer.start(); 196 | const start = timer.lap(); 197 | while (i < count) : (i += 1) { 198 | for (data1.items) |b| { 199 | for (data0.items) |a| { 200 | const r = [3]f32{ 201 | 0.01 * (a[1] * b[2] - a[2] * b[1]) + 1.0, 202 | 0.01 * (a[2] * b[0] - a[0] * b[2]) + 1.0, 203 | 0.01 * (a[0] * b[1] - a[1] * b[0]) + 1.0, 204 | }; 205 | std.mem.doNotOptimizeAway(&r); 206 | } 207 | } 208 | } 209 | const end = timer.read(); 210 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 211 | 212 | std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s}); 213 | } 214 | 215 | { 216 | i = 0; 217 | var timer = try Timer.start(); 218 | const start = timer.lap(); 219 | while (i < count) : (i += 1) { 220 | for (data1.items) |b| { 221 | for (data0.items) |a| { 222 | const va = zm.loadArr3(a); 223 | const vb = zm.loadArr3(b); 224 | const cp = zm.f32x4s(0.01) * zm.cross3(va, vb) + zm.f32x4s(1.0); 225 | std.mem.doNotOptimizeAway(&cp); 226 | } 227 | } 228 | } 229 | const end = timer.read(); 230 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 231 | 232 | std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s}); 233 | } 234 | } 235 | 236 | noinline fn cross3Dot3ScaleBiasBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void { 237 | std.debug.print("{s:>42} - ", .{"cross3, dot3, scale, bias benchmark (AOS)"}); 238 | 239 | var data0 = std.ArrayList([3]f32).init(allocator); 240 | defer data0.deinit(); 241 | var data1 = std.ArrayList([3]f32).init(allocator); 242 | defer data1.deinit(); 243 | 244 | var i: usize = 0; 245 | while (i < 256) : (i += 1) { 246 | try data0.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) }); 247 | try data1.append([3]f32{ random.float(f32), random.float(f32), random.float(f32) }); 248 | } 249 | 250 | // Warmup, fills L1 cache. 251 | i = 0; 252 | while (i < 100) : (i += 1) { 253 | for (data1.items) |b| { 254 | for (data0.items) |a| { 255 | const va = zm.loadArr3(a); 256 | const vb = zm.loadArr3(b); 257 | const r = (zm.dot3(va, vb) * (zm.f32x4s(0.1) * zm.cross3(va, vb) + zm.f32x4s(1.0)))[0]; 258 | std.mem.doNotOptimizeAway(&r); 259 | } 260 | } 261 | } 262 | 263 | { 264 | i = 0; 265 | var timer = try Timer.start(); 266 | const start = timer.lap(); 267 | while (i < count) : (i += 1) { 268 | for (data1.items) |b| { 269 | for (data0.items) |a| { 270 | const d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; 271 | const r = [3]f32{ 272 | d * (0.1 * (a[1] * b[2] - a[2] * b[1]) + 1.0), 273 | d * (0.1 * (a[2] * b[0] - a[0] * b[2]) + 1.0), 274 | d * (0.1 * (a[0] * b[1] - a[1] * b[0]) + 1.0), 275 | }; 276 | std.mem.doNotOptimizeAway(&r); 277 | } 278 | } 279 | } 280 | const end = timer.read(); 281 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 282 | 283 | std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s}); 284 | } 285 | 286 | { 287 | i = 0; 288 | var timer = try Timer.start(); 289 | const start = timer.lap(); 290 | while (i < count) : (i += 1) { 291 | for (data1.items) |b| { 292 | for (data0.items) |a| { 293 | const va = zm.loadArr3(a); 294 | const vb = zm.loadArr3(b); 295 | const r = zm.dot3(va, vb) * (zm.f32x4s(0.1) * zm.cross3(va, vb) + zm.f32x4s(1.0)); 296 | std.mem.doNotOptimizeAway(&r); 297 | } 298 | } 299 | } 300 | const end = timer.read(); 301 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 302 | 303 | std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s}); 304 | } 305 | } 306 | 307 | noinline fn quatBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void { 308 | std.debug.print("{s:>42} - ", .{"quaternion mul benchmark (AOS)"}); 309 | 310 | var data0 = std.ArrayList([4]f32).init(allocator); 311 | defer data0.deinit(); 312 | var data1 = std.ArrayList([4]f32).init(allocator); 313 | defer data1.deinit(); 314 | 315 | var i: usize = 0; 316 | while (i < 256) : (i += 1) { 317 | try data0.append([4]f32{ random.float(f32), random.float(f32), random.float(f32), random.float(f32) }); 318 | try data1.append([4]f32{ random.float(f32), random.float(f32), random.float(f32), random.float(f32) }); 319 | } 320 | 321 | // Warmup, fills L1 cache. 322 | i = 0; 323 | while (i < 100) : (i += 1) { 324 | for (data1.items) |b| { 325 | for (data0.items) |a| { 326 | const va = zm.loadArr4(a); 327 | const vb = zm.loadArr4(b); 328 | const r = zm.qmul(va, vb); 329 | std.mem.doNotOptimizeAway(&r); 330 | } 331 | } 332 | } 333 | 334 | { 335 | i = 0; 336 | var timer = try Timer.start(); 337 | const start = timer.lap(); 338 | while (i < count) : (i += 1) { 339 | for (data1.items) |b| { 340 | for (data0.items) |a| { 341 | const r = [4]f32{ 342 | (b[3] * a[0]) + (b[0] * a[3]) + (b[1] * a[2]) - (b[2] * a[1]), 343 | (b[3] * a[1]) - (b[0] * a[2]) + (b[1] * a[3]) + (b[2] * a[0]), 344 | (b[3] * a[2]) + (b[0] * a[1]) - (b[1] * a[0]) + (b[2] * a[3]), 345 | (b[3] * a[3]) - (b[0] * a[0]) - (b[1] * a[1]) - (b[2] * a[2]), 346 | }; 347 | std.mem.doNotOptimizeAway(&r); 348 | } 349 | } 350 | } 351 | const end = timer.read(); 352 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 353 | 354 | std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s}); 355 | } 356 | 357 | { 358 | i = 0; 359 | var timer = try Timer.start(); 360 | const start = timer.lap(); 361 | while (i < count) : (i += 1) { 362 | for (data1.items) |b| { 363 | for (data0.items) |a| { 364 | const va = zm.loadArr4(a); 365 | const vb = zm.loadArr4(b); 366 | const r = zm.qmul(va, vb); 367 | std.mem.doNotOptimizeAway(&r); 368 | } 369 | } 370 | } 371 | const end = timer.read(); 372 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 373 | 374 | std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s}); 375 | } 376 | } 377 | 378 | noinline fn waveBenchmark(allocator: std.mem.Allocator, comptime count: comptime_int) !void { 379 | _ = allocator; 380 | std.debug.print("{s:>42} - ", .{"wave benchmark (SOA)"}); 381 | 382 | const grid_size = 1024; 383 | { 384 | var t: f32 = 0.0; 385 | 386 | const scale: f32 = 0.05; 387 | 388 | var timer = try Timer.start(); 389 | const start = timer.lap(); 390 | 391 | var iter: usize = 0; 392 | while (iter < count) : (iter += 1) { 393 | var z_index: i32 = 0; 394 | while (z_index < grid_size) : (z_index += 1) { 395 | const z = scale * @intToFloat(f32, z_index - grid_size / 2); 396 | 397 | var x_index: i32 = 0; 398 | while (x_index < grid_size) : (x_index += 4) { 399 | const x0 = scale * @intToFloat(f32, x_index + 0 - grid_size / 2); 400 | const x1 = scale * @intToFloat(f32, x_index + 1 - grid_size / 2); 401 | const x2 = scale * @intToFloat(f32, x_index + 2 - grid_size / 2); 402 | const x3 = scale * @intToFloat(f32, x_index + 3 - grid_size / 2); 403 | 404 | const d0 = zm.sqrt(x0 * x0 + z * z); 405 | const d1 = zm.sqrt(x1 * x1 + z * z); 406 | const d2 = zm.sqrt(x2 * x2 + z * z); 407 | const d3 = zm.sqrt(x3 * x3 + z * z); 408 | 409 | const y0 = zm.sin(d0 - t); 410 | const y1 = zm.sin(d1 - t); 411 | const y2 = zm.sin(d2 - t); 412 | const y3 = zm.sin(d3 - t); 413 | 414 | std.mem.doNotOptimizeAway(&y0); 415 | std.mem.doNotOptimizeAway(&y1); 416 | std.mem.doNotOptimizeAway(&y2); 417 | std.mem.doNotOptimizeAway(&y3); 418 | } 419 | } 420 | t += 0.001; 421 | } 422 | const end = timer.read(); 423 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 424 | 425 | std.debug.print("scalar version: {d:.4}s, ", .{elapsed_s}); 426 | } 427 | 428 | { 429 | const T = zm.F32x16; 430 | 431 | const static = struct { 432 | const offsets = [16]f32{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; 433 | }; 434 | const voffset = zm.load(static.offsets[0..], T, 0); 435 | var vt = zm.splat(T, 0.0); 436 | 437 | const scale: f32 = 0.05; 438 | 439 | var timer = try Timer.start(); 440 | const start = timer.lap(); 441 | 442 | var iter: usize = 0; 443 | while (iter < count) : (iter += 1) { 444 | var z_index: i32 = 0; 445 | while (z_index < grid_size) : (z_index += 1) { 446 | const z = scale * @intToFloat(f32, z_index - grid_size / 2); 447 | const vz = zm.splat(T, z); 448 | 449 | var x_index: i32 = 0; 450 | while (x_index < grid_size) : (x_index += zm.veclen(T)) { 451 | const x = scale * @intToFloat(f32, x_index - grid_size / 2); 452 | const vx = zm.splat(T, x) + voffset * zm.splat(T, scale); 453 | 454 | const d = zm.sqrt(vx * vx + vz * vz); 455 | 456 | const vy = zm.sin(d - vt); 457 | 458 | std.mem.doNotOptimizeAway(&vy); 459 | } 460 | } 461 | vt += zm.splat(T, 0.001); 462 | } 463 | const end = timer.read(); 464 | const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; 465 | 466 | std.debug.print("zmath version: {d:.4}s\n", .{elapsed_s}); 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /libs/zmath/src/main.zig: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------------------------------------------- 2 | // 3 | // SIMD math library for game developers 4 | // https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath 5 | // 6 | // See zmath.zig for more details. 7 | // See util.zig for additional functionality. 8 | // 9 | //-------------------------------------------------------------------------------------------------- 10 | pub const version = @import("std").SemanticVersion{ .major = 0, .minor = 9, .patch = 5 }; 11 | 12 | pub usingnamespace @import("zmath.zig"); 13 | pub const util = @import("util.zig"); 14 | 15 | // ensure transitive closure of test coverage 16 | comptime { 17 | _ = util; 18 | } 19 | -------------------------------------------------------------------------------- /libs/zmath/src/util.zig: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // 3 | // Collection of useful functions building on top of, and extending, core zmath. 4 | // https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath 5 | // 6 | // ------------------------------------------------------------------------------ 7 | // 1. Matrix functions 8 | // ------------------------------------------------------------------------------ 9 | // 10 | // As an example, in a left handed Y-up system: 11 | // getAxisX is equivalent to the right vector 12 | // getAxisY is equivalent to the up vector 13 | // getAxisZ is equivalent to the forward vector 14 | // 15 | // getTranslationVec(m: Mat) Vec 16 | // getAxisX(m: Mat) Vec 17 | // getAxisY(m: Mat) Vec 18 | // getAxisZ(m: Mat) Vec 19 | // 20 | // 21 | // ------------------------------------------------------------------------------ 22 | // 2. Angle functions 23 | // ------------------------------------------------------------------------------ 24 | // 25 | // angleMod(angle: f32) f32 26 | // 27 | // ============================================================================== 28 | 29 | const zm = @import("zmath.zig"); 30 | const std = @import("std"); 31 | const math = std.math; 32 | const expect = std.testing.expect; 33 | 34 | pub fn getTranslationVec(m: zm.Mat) zm.Vec { 35 | var translation = m[3]; 36 | translation[3] = 0; 37 | return translation; 38 | } 39 | 40 | pub fn getScaleVec(m: zm.Mat) zm.Vec { 41 | const scale_x = zm.length3(zm.f32x4(m[0][0], m[1][0], m[2][0], 0))[0]; 42 | const scale_y = zm.length3(zm.f32x4(m[0][1], m[1][1], m[2][1], 0))[0]; 43 | const scale_z = zm.length3(zm.f32x4(m[0][2], m[1][2], m[2][2], 0))[0]; 44 | return zm.f32x4(scale_x, scale_y, scale_z, 0); 45 | } 46 | 47 | pub fn getRotationQuat(_m: zm.Mat) zm.Quat { 48 | // Ortho normalize given matrix. 49 | const c1 = zm.normalize3(zm.f32x4(_m[0][0], _m[1][0], _m[2][0], 0)); 50 | const c2 = zm.normalize3(zm.f32x4(_m[0][1], _m[1][1], _m[2][1], 0)); 51 | const c3 = zm.normalize3(zm.f32x4(_m[0][2], _m[1][2], _m[2][2], 0)); 52 | var m = _m; 53 | m[0][0] = c1[0]; 54 | m[1][0] = c1[1]; 55 | m[2][0] = c1[2]; 56 | m[0][1] = c2[0]; 57 | m[1][1] = c2[1]; 58 | m[2][1] = c2[2]; 59 | m[0][2] = c3[0]; 60 | m[1][2] = c3[1]; 61 | m[2][2] = c3[2]; 62 | 63 | // Extract rotation 64 | return zm.quatFromMat(m); 65 | } 66 | 67 | pub fn getAxisX(m: zm.Mat) zm.Vec { 68 | return zm.normalize3(zm.f32x4(m[0][0], m[0][1], m[0][2], 0.0)); 69 | } 70 | 71 | pub fn getAxisY(m: zm.Mat) zm.Vec { 72 | return zm.normalize3(zm.f32x4(m[1][0], m[1][1], m[1][2], 0.0)); 73 | } 74 | 75 | pub fn getAxisZ(m: zm.Mat) zm.Vec { 76 | return zm.normalize3(zm.f32x4(m[2][0], m[2][1], m[2][2], 0.0)); 77 | } 78 | 79 | test "zmath.util.mat.translation" { 80 | // zig fmt: off 81 | const mat_data = [18]f32{ 82 | 1.0, 83 | 2.0, 3.0, 4.0, 5.0, 84 | 6.0, 7.0, 8.0, 9.0, 85 | 10.0,11.0, 12.0,13.0, 86 | 14.0, 15.0, 16.0, 17.0, 87 | 18.0, 88 | }; 89 | // zig fmt: on 90 | const mat = zm.loadMat(mat_data[1..]); 91 | const translation = getTranslationVec(mat); 92 | try expect(zm.approxEqAbs(translation, zm.f32x4(14.0, 15.0, 16.0, 0.0), 0.0001)); 93 | } 94 | 95 | test "zmath.util.mat.scale" { 96 | const mat = zm.mul(zm.scaling(3, 4, 5), zm.translation(6, 7, 8)); 97 | const scale = getScaleVec(mat); 98 | try expect(zm.approxEqAbs(scale, zm.f32x4(3.0, 4.0, 5.0, 0.0), 0.0001)); 99 | } 100 | 101 | test "zmath.util.mat.rotation" { 102 | const rotate_origin = zm.matFromRollPitchYaw(0.1, 1.2, 2.3); 103 | const mat = zm.mul(zm.mul(rotate_origin, zm.scaling(3, 4, 5)), zm.translation(6, 7, 8)); 104 | const rotate_get = getRotationQuat(mat); 105 | const v0 = zm.mul(zm.f32x4s(1), rotate_origin); 106 | const v1 = zm.mul(zm.f32x4s(1), zm.quatToMat(rotate_get)); 107 | try expect(zm.approxEqAbs(v0, v1, 0.0001)); 108 | } 109 | 110 | test "zmath.util.mat.z_vec" { 111 | const degToRad = std.math.degreesToRadians; 112 | var identity = zm.identity(); 113 | var z_vec = getAxisZ(identity); 114 | try expect(zm.approxEqAbs(z_vec, zm.f32x4(0.0, 0.0, 1.0, 0), 0.0001)); 115 | const rot_yaw = zm.rotationY(degToRad(f32, 90)); 116 | identity = zm.mul(identity, rot_yaw); 117 | z_vec = getAxisZ(identity); 118 | try expect(zm.approxEqAbs(z_vec, zm.f32x4(1.0, 0.0, 0.0, 0), 0.0001)); 119 | } 120 | 121 | test "zmath.util.mat.y_vec" { 122 | const degToRad = std.math.degreesToRadians; 123 | var identity = zm.identity(); 124 | var y_vec = getAxisY(identity); 125 | try expect(zm.approxEqAbs(y_vec, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01)); 126 | const rot_yaw = zm.rotationY(degToRad(f32, 90)); 127 | identity = zm.mul(identity, rot_yaw); 128 | y_vec = getAxisY(identity); 129 | try expect(zm.approxEqAbs(y_vec, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01)); 130 | const rot_pitch = zm.rotationX(degToRad(f32, 90)); 131 | identity = zm.mul(identity, rot_pitch); 132 | y_vec = getAxisY(identity); 133 | try expect(zm.approxEqAbs(y_vec, zm.f32x4(0.0, 0.0, 1.0, 0), 0.01)); 134 | } 135 | 136 | test "zmath.util.mat.right" { 137 | const degToRad = std.math.degreesToRadians; 138 | var identity = zm.identity(); 139 | var right = getAxisX(identity); 140 | try expect(zm.approxEqAbs(right, zm.f32x4(1.0, 0.0, 0.0, 0), 0.01)); 141 | const rot_yaw = zm.rotationY(degToRad(f32, 90)); 142 | identity = zm.mul(identity, rot_yaw); 143 | right = getAxisX(identity); 144 | try expect(zm.approxEqAbs(right, zm.f32x4(0.0, 0.0, -1.0, 0), 0.01)); 145 | const rot_pitch = zm.rotationX(degToRad(f32, 90)); 146 | identity = zm.mul(identity, rot_pitch); 147 | right = getAxisX(identity); 148 | try expect(zm.approxEqAbs(right, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01)); 149 | } 150 | 151 | // ------------------------------------------------------------------------------ 152 | // This software is available under 2 licenses -- choose whichever you prefer. 153 | // ------------------------------------------------------------------------------ 154 | // ALTERNATIVE A - MIT License 155 | // Copyright (c) 2022 Michal Ziulek and Contributors 156 | // Permission is hereby granted, free of charge, to any person obtaining identity copy of 157 | // this software and associated documentation files (the "Software"), to deal in 158 | // the Software without restriction, including without limitation the rights to 159 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 160 | // of the Software, and to permit persons to whom the Software is furnished to do 161 | // so, subject to the following conditions: 162 | // The above copyright notice and this permission notice shall be included in all 163 | // copies or substantial portions of the Software. 164 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 165 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 166 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 167 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 168 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 169 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 170 | // SOFTWARE. 171 | // ------------------------------------------------------------------------------ 172 | // ALTERNATIVE B - Public Domain (www.unlicense.org) 173 | // This is free and unencumbered software released into the public domain. 174 | // Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 175 | // software, either in source code form or as identity compiled binary, for any purpose, 176 | // commercial or non-commercial, and by any means. 177 | // In jurisdictions that recognize copyright laws, the author or authors of this 178 | // software dedicate any and all copyright interest in the software to the public 179 | // domain. We make this dedication for the benefit of the public at large and to 180 | // the detriment of our heirs and successors. We intend this dedication to be an 181 | // overt act of relinquishment in perpetuity of all present and future rights to 182 | // this software under copyright law. 183 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 184 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 185 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 186 | // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 187 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 188 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 189 | // ------------------------------------------------------------------------------ 190 | -------------------------------------------------------------------------------- /libs/zstbi/README.md: -------------------------------------------------------------------------------- 1 | # zstbi v0.9.2 - stb image bindings 2 | 3 | ## Features 4 | 5 | * Supports Zig memory allocators 6 | * Supports decoding most popular formats 7 | * Supports HDR images 8 | * Supports 8-bits and 16-bits per channel 9 | * Supports image resizing 10 | * Supports image writing (.png, .jpg) 11 | 12 | ## Getting started 13 | 14 | Copy `zstbi` folder to a `libs` subdirectory of the root of your project. 15 | 16 | Then in your `build.zig` add: 17 | ```zig 18 | const zstbi = @import("libs/zstbi/build.zig"); 19 | 20 | pub fn build(b: *std.Build) void { 21 | ... 22 | const zstbi_pkg = zstbi.package(b, .{}); 23 | 24 | exe.addModule("zstbi", zstbi_pkg.module); 25 | 26 | zstbi.link(exe); 27 | } 28 | ``` 29 | Now in your code you may import and use `zstbi`. 30 | 31 | Init the lib. `zstbi.init()` is cheap and you may call it whenever you need to change memory allocator. Must be called from the main thread. 32 | ```zig 33 | const zstbi = @import("zstbi"); 34 | 35 | zstbi.init(allocator); 36 | defer zstbi.deinit(); 37 | ``` 38 | 39 | Load image: 40 | ```zig 41 | var image = try zstbi.Image.init("data/image.png", num_desired_channels); 42 | defer image.deinit(); 43 | _ = image.data; // stored as []u8 44 | _ = image.width; 45 | _ = image.height; 46 | _ = image.num_components; 47 | _ = image.bytes_per_component; 48 | _ = image.bytes_per_row; 49 | _ = image.is_hdr; 50 | 51 | const new_resized_image = image.resize(1024, 1024); 52 | ``` 53 | 54 | Get image info without loading: 55 | ```zig 56 | const image_info = zstbi.Image.info("data/image.jpg"); 57 | _ = image_info.is_supported; // Is image format supported? 58 | _ = image_info.width; 59 | _ = image_info.height; 60 | _ = image_info.num_components; 61 | ``` 62 | Misc functions: 63 | ```zig 64 | pub fn isHdr(filename: [:0]const u8) bool 65 | pub fn is16bit(filename: [:0]const u8) bool 66 | 67 | pub fn setFlipVerticallyOnLoad(should_flip: bool) void 68 | ``` 69 | -------------------------------------------------------------------------------- /libs/zstbi/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Package = struct { 4 | module: *std.Build.Module, 5 | }; 6 | 7 | pub fn package(b: *std.Build, _: struct {}) Package { 8 | const module = b.createModule(.{ 9 | .source_file = .{ .path = thisDir() ++ "/src/zstbi.zig" }, 10 | }); 11 | return .{ .module = module }; 12 | } 13 | 14 | pub fn build(_: *std.Build) void {} 15 | 16 | pub fn buildTests( 17 | b: *std.Build, 18 | build_mode: std.builtin.Mode, 19 | target: std.zig.CrossTarget, 20 | ) *std.Build.CompileStep { 21 | const tests = b.addTest(.{ 22 | .root_source_file = .{ .path = thisDir() ++ "/src/zstbi.zig" }, 23 | .target = target, 24 | .optimize = build_mode, 25 | }); 26 | link(tests); 27 | return tests; 28 | } 29 | 30 | pub fn link(exe: *std.Build.CompileStep) void { 31 | exe.linkSystemLibraryName("c"); 32 | exe.addCSourceFile(thisDir() ++ "/libs/stbi/stb_image.c", &.{ 33 | "-std=c99", 34 | "-fno-sanitize=undefined", 35 | }); 36 | } 37 | 38 | inline fn thisDir() []const u8 { 39 | return comptime std.fs.path.dirname(@src().file) orelse "."; 40 | } 41 | -------------------------------------------------------------------------------- /libs/zstbi/libs/stbi/stb_image.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void* (*zstbiMallocPtr)(size_t size) = NULL; 4 | void* (*zstbiReallocPtr)(void* ptr, size_t size) = NULL; 5 | void (*zstbiFreePtr)(void* ptr) = NULL; 6 | 7 | #define STBI_MALLOC(size) zstbiMallocPtr(size) 8 | #define STBI_REALLOC(ptr, size) zstbiReallocPtr(ptr, size) 9 | #define STBI_FREE(ptr) zstbiFreePtr(ptr) 10 | 11 | #define STB_IMAGE_IMPLEMENTATION 12 | #include "stb_image.h" 13 | 14 | void* (*zstbirMallocPtr)(size_t size, void* context) = NULL; 15 | void (*zstbirFreePtr)(void* ptr, void* context) = NULL; 16 | 17 | #define STBIR_MALLOC(size, context) zstbirMallocPtr(size, context) 18 | #define STBIR_FREE(ptr, context) zstbirFreePtr(ptr, context) 19 | 20 | #define STB_IMAGE_RESIZE_IMPLEMENTATION 21 | #include "stb_image_resize.h" 22 | 23 | void* (*zstbiwMallocPtr)(size_t size) = NULL; 24 | void* (*zstbiwReallocPtr)(void* ptr, size_t size) = NULL; 25 | void (*zstbiwFreePtr)(void* ptr) = NULL; 26 | 27 | #define STBIW_MALLOC(size) zstbiwMallocPtr(size) 28 | #define STBIW_REALLOC(ptr, size) zstbiwReallocPtr(ptr, size) 29 | #define STBIW_FREE(ptr) zstbiwFreePtr(ptr) 30 | 31 | #define STB_IMAGE_WRITE_IMPLEMENTATION 32 | #include "stb_image_write.h" 33 | -------------------------------------------------------------------------------- /libs/zstbi/src/zstbi.zig: -------------------------------------------------------------------------------- 1 | pub const version = @import("std").SemanticVersion{ .major = 0, .minor = 9, .patch = 2 }; 2 | const std = @import("std"); 3 | const assert = std.debug.assert; 4 | 5 | pub fn init(allocator: std.mem.Allocator) void { 6 | assert(mem_allocator == null); 7 | mem_allocator = allocator; 8 | mem_allocations = std.AutoHashMap(usize, usize).init(allocator); 9 | 10 | // stb image 11 | zstbiMallocPtr = zstbiMalloc; 12 | zstbiReallocPtr = zstbiRealloc; 13 | zstbiFreePtr = zstbiFree; 14 | // stb image resize 15 | zstbirMallocPtr = zstbirMalloc; 16 | zstbirFreePtr = zstbirFree; 17 | // stb image write 18 | zstbiwMallocPtr = zstbiMalloc; 19 | zstbiwReallocPtr = zstbiRealloc; 20 | zstbiwFreePtr = zstbiFree; 21 | } 22 | 23 | pub fn deinit() void { 24 | assert(mem_allocator != null); 25 | assert(mem_allocations.?.count() == 0); 26 | mem_allocations.?.deinit(); 27 | mem_allocations = null; 28 | mem_allocator = null; 29 | } 30 | 31 | pub const JpgWriteSettings = struct { 32 | quality: u32, 33 | }; 34 | 35 | pub const ImageWriteFormat = union(enum) { 36 | png, 37 | jpg: JpgWriteSettings, 38 | }; 39 | 40 | pub const ImageWriteError = error{ 41 | CouldNotWriteImage, 42 | }; 43 | 44 | pub const Image = struct { 45 | data: []u8, 46 | width: u32, 47 | height: u32, 48 | num_components: u32, 49 | bytes_per_component: u32, 50 | bytes_per_row: u32, 51 | is_hdr: bool, 52 | 53 | pub fn info(pathname: [:0]const u8) struct { 54 | is_supported: bool, 55 | width: u32, 56 | height: u32, 57 | num_components: u32, 58 | } { 59 | var w: c_int = 0; 60 | var h: c_int = 0; 61 | var c: c_int = 0; 62 | const is_supported = stbi_info(pathname, &w, &h, &c); 63 | return .{ 64 | .is_supported = is_supported, 65 | .width = @intCast(u32, w), 66 | .height = @intCast(u32, h), 67 | .num_components = @intCast(u32, c), 68 | }; 69 | } 70 | 71 | pub fn init(pathname: [:0]const u8, forced_num_channels: u32) !Image { 72 | var width: u32 = 0; 73 | var height: u32 = 0; 74 | var num_components: u32 = 0; 75 | var bytes_per_component: u32 = 0; 76 | var bytes_per_row: u32 = 0; 77 | var is_hdr = false; 78 | 79 | const data = if (isHdr(pathname)) data: { 80 | var x: c_int = undefined; 81 | var y: c_int = undefined; 82 | var ch: c_int = undefined; 83 | const ptr = stbi_loadf( 84 | pathname, 85 | &x, 86 | &y, 87 | &ch, 88 | @intCast(c_int, forced_num_channels), 89 | ); 90 | if (ptr == null) return error.ImageInitFailed; 91 | 92 | num_components = if (forced_num_channels == 0) @intCast(u32, ch) else forced_num_channels; 93 | width = @intCast(u32, x); 94 | height = @intCast(u32, y); 95 | bytes_per_component = 2; 96 | bytes_per_row = width * num_components * bytes_per_component; 97 | is_hdr = true; 98 | 99 | // Convert each component from f32 to f16. 100 | var ptr_f16 = @ptrCast([*]f16, ptr.?); 101 | const num = width * height * num_components; 102 | var i: u32 = 0; 103 | while (i < num) : (i += 1) { 104 | ptr_f16[i] = @floatCast(f16, ptr.?[i]); 105 | } 106 | break :data @ptrCast([*]u8, ptr_f16)[0 .. height * bytes_per_row]; 107 | } else data: { 108 | var x: c_int = undefined; 109 | var y: c_int = undefined; 110 | var ch: c_int = undefined; 111 | const is_16bit = is16bit(pathname); 112 | const ptr = if (is_16bit) @ptrCast(?[*]u8, stbi_load_16( 113 | pathname, 114 | &x, 115 | &y, 116 | &ch, 117 | @intCast(c_int, forced_num_channels), 118 | )) else stbi_load( 119 | pathname, 120 | &x, 121 | &y, 122 | &ch, 123 | @intCast(c_int, forced_num_channels), 124 | ); 125 | if (ptr == null) return error.ImageInitFailed; 126 | 127 | num_components = if (forced_num_channels == 0) @intCast(u32, ch) else forced_num_channels; 128 | width = @intCast(u32, x); 129 | height = @intCast(u32, y); 130 | bytes_per_component = if (is_16bit) 2 else 1; 131 | bytes_per_row = width * num_components * bytes_per_component; 132 | is_hdr = false; 133 | 134 | break :data @ptrCast([*]u8, ptr)[0 .. height * bytes_per_row]; 135 | }; 136 | 137 | return Image{ 138 | .data = data, 139 | .width = width, 140 | .height = height, 141 | .num_components = num_components, 142 | .bytes_per_component = bytes_per_component, 143 | .bytes_per_row = bytes_per_row, 144 | .is_hdr = is_hdr, 145 | }; 146 | } 147 | 148 | pub fn initFromData(data: []const u8, forced_num_channels: u32) !Image { 149 | // TODO: Add support for HDR images (https://github.com/michal-z/zig-gamedev/issues/155). 150 | var width: u32 = 0; 151 | var height: u32 = 0; 152 | var num_components: u32 = 0; 153 | var bytes_per_component: u32 = 0; 154 | var bytes_per_row: u32 = 0; 155 | 156 | const image_data = data: { 157 | var x: c_int = undefined; 158 | var y: c_int = undefined; 159 | var ch: c_int = undefined; 160 | const ptr = stbi_load_from_memory( 161 | data.ptr, 162 | @intCast(c_int, data.len), 163 | &x, 164 | &y, 165 | &ch, 166 | @intCast(c_int, forced_num_channels), 167 | ); 168 | if (ptr == null) return error.ImageInitFailed; 169 | 170 | num_components = if (forced_num_channels == 0) @intCast(u32, ch) else forced_num_channels; 171 | width = @intCast(u32, x); 172 | height = @intCast(u32, y); 173 | bytes_per_component = 1; 174 | bytes_per_row = width * num_components * bytes_per_component; 175 | 176 | break :data @ptrCast([*]u8, ptr)[0 .. height * bytes_per_row]; 177 | }; 178 | 179 | return Image{ 180 | .data = image_data, 181 | .width = width, 182 | .height = height, 183 | .num_components = num_components, 184 | .bytes_per_component = bytes_per_component, 185 | .bytes_per_row = bytes_per_row, 186 | .is_hdr = false, 187 | }; 188 | } 189 | 190 | pub fn resize(image: *const Image, new_width: u32, new_height: u32) Image { 191 | // TODO: Add support for HDR images 192 | const new_bytes_per_row = new_width * image.num_components * image.bytes_per_component; 193 | const new_size = new_height * new_bytes_per_row; 194 | const new_data = @ptrCast([*]u8, zstbiMalloc(new_size)); 195 | stbir_resize_uint8( 196 | image.data.ptr, 197 | @intCast(c_int, image.width), 198 | @intCast(c_int, image.height), 199 | 0, 200 | new_data, 201 | @intCast(c_int, new_width), 202 | @intCast(c_int, new_height), 203 | 0, 204 | @intCast(c_int, image.num_components), 205 | ); 206 | return .{ 207 | .data = new_data[0..new_size], 208 | .width = new_width, 209 | .height = new_height, 210 | .num_components = image.num_components, 211 | .bytes_per_component = image.bytes_per_component, 212 | .bytes_per_row = new_bytes_per_row, 213 | .is_hdr = image.is_hdr, 214 | }; 215 | } 216 | 217 | pub fn writeToFile( 218 | self: *const Image, 219 | filename: [:0]const u8, 220 | image_format: ImageWriteFormat, 221 | ) ImageWriteError!void { 222 | const w = @intCast(c_int, self.width); 223 | const h = @intCast(c_int, self.height); 224 | const comp = @intCast(c_int, self.num_components); 225 | const result = switch (image_format) { 226 | .png => stbi_write_png(filename.ptr, w, h, comp, self.data.ptr, 0), 227 | .jpg => |settings| stbi_write_jpg( 228 | filename.ptr, 229 | w, 230 | h, 231 | comp, 232 | self.data.ptr, 233 | @intCast(c_int, settings.quality), 234 | ), 235 | }; 236 | // if the result is 0 then it means an error occured (per stb image write docs) 237 | if (result == 0) { 238 | return ImageWriteError.CouldNotWriteImage; 239 | } 240 | } 241 | 242 | pub fn deinit(image: *Image) void { 243 | stbi_image_free(image.data.ptr); 244 | image.* = undefined; 245 | } 246 | }; 247 | 248 | /// `pub fn setHdrToLdrScale(scale: f32) void` 249 | pub const setHdrToLdrScale = stbi_hdr_to_ldr_scale; 250 | 251 | /// `pub fn setHdrToLdrGamma(gamma: f32) void` 252 | pub const setHdrToLdrGamma = stbi_hdr_to_ldr_gamma; 253 | 254 | /// `pub fn setLdrToHdrScale(scale: f32) void` 255 | pub const setLdrToHdrScale = stbi_ldr_to_hdr_scale; 256 | 257 | /// `pub fn setLdrToHdrGamma(gamma: f32) void` 258 | pub const setLdrToHdrGamma = stbi_ldr_to_hdr_gamma; 259 | 260 | pub fn isHdr(filename: [:0]const u8) bool { 261 | return stbi_is_hdr(filename) != 0; 262 | } 263 | 264 | pub fn is16bit(filename: [:0]const u8) bool { 265 | return stbi_is_16_bit(filename) != 0; 266 | } 267 | 268 | pub fn setFlipVerticallyOnLoad(should_flip: bool) void { 269 | stbi_set_flip_vertically_on_load(if (should_flip) 1 else 0); 270 | } 271 | 272 | var mem_allocator: ?std.mem.Allocator = null; 273 | var mem_allocations: ?std.AutoHashMap(usize, usize) = null; 274 | var mem_mutex: std.Thread.Mutex = .{}; 275 | const mem_alignment = 16; 276 | 277 | extern var zstbiMallocPtr: ?*const fn (size: usize) callconv(.C) ?*anyopaque; 278 | extern var zstbiwMallocPtr: ?*const fn (size: usize) callconv(.C) ?*anyopaque; 279 | 280 | fn zstbiMalloc(size: usize) callconv(.C) ?*anyopaque { 281 | mem_mutex.lock(); 282 | defer mem_mutex.unlock(); 283 | 284 | const mem = mem_allocator.?.alignedAlloc( 285 | u8, 286 | mem_alignment, 287 | size, 288 | ) catch @panic("zstbi: out of memory"); 289 | 290 | mem_allocations.?.put(@ptrToInt(mem.ptr), size) catch @panic("zstbi: out of memory"); 291 | 292 | return mem.ptr; 293 | } 294 | 295 | extern var zstbiReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque; 296 | extern var zstbiwReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque; 297 | 298 | fn zstbiRealloc(ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque { 299 | mem_mutex.lock(); 300 | defer mem_mutex.unlock(); 301 | 302 | const old_size = if (ptr != null) mem_allocations.?.get(@ptrToInt(ptr.?)).? else 0; 303 | const old_mem = if (old_size > 0) 304 | @ptrCast([*]align(mem_alignment) u8, @alignCast(mem_alignment, ptr))[0..old_size] 305 | else 306 | @as([*]align(mem_alignment) u8, undefined)[0..0]; 307 | 308 | const new_mem = mem_allocator.?.realloc(old_mem, size) catch @panic("zstbi: out of memory"); 309 | 310 | if (ptr != null) { 311 | const removed = mem_allocations.?.remove(@ptrToInt(ptr.?)); 312 | std.debug.assert(removed); 313 | } 314 | 315 | mem_allocations.?.put(@ptrToInt(new_mem.ptr), size) catch @panic("zstbi: out of memory"); 316 | 317 | return new_mem.ptr; 318 | } 319 | 320 | extern var zstbiFreePtr: ?*const fn (maybe_ptr: ?*anyopaque) callconv(.C) void; 321 | extern var zstbiwFreePtr: ?*const fn (maybe_ptr: ?*anyopaque) callconv(.C) void; 322 | 323 | fn zstbiFree(maybe_ptr: ?*anyopaque) callconv(.C) void { 324 | if (maybe_ptr) |ptr| { 325 | mem_mutex.lock(); 326 | defer mem_mutex.unlock(); 327 | 328 | const size = mem_allocations.?.fetchRemove(@ptrToInt(ptr)).?.value; 329 | const mem = @ptrCast([*]align(mem_alignment) u8, @alignCast(mem_alignment, ptr))[0..size]; 330 | mem_allocator.?.free(mem); 331 | } 332 | } 333 | 334 | extern var zstbirMallocPtr: ?*const fn (size: usize, maybe_context: ?*anyopaque) callconv(.C) ?*anyopaque; 335 | 336 | fn zstbirMalloc(size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque { 337 | return zstbiMalloc(size); 338 | } 339 | 340 | extern var zstbirFreePtr: ?*const fn (maybe_ptr: ?*anyopaque, maybe_context: ?*anyopaque) callconv(.C) void; 341 | 342 | fn zstbirFree(maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.C) void { 343 | zstbiFree(maybe_ptr); 344 | } 345 | 346 | extern fn stbi_info(filename: [*:0]const u8, x: *c_int, y: *c_int, comp: *c_int) c_int; 347 | 348 | extern fn stbi_load( 349 | filename: [*:0]const u8, 350 | x: *c_int, 351 | y: *c_int, 352 | channels_in_file: *c_int, 353 | desired_channels: c_int, 354 | ) ?[*]u8; 355 | 356 | extern fn stbi_load_16( 357 | filename: [*:0]const u8, 358 | x: *c_int, 359 | y: *c_int, 360 | channels_in_file: *c_int, 361 | desired_channels: c_int, 362 | ) ?[*]u16; 363 | 364 | extern fn stbi_loadf( 365 | filename: [*:0]const u8, 366 | x: *c_int, 367 | y: *c_int, 368 | channels_in_file: *c_int, 369 | desired_channels: c_int, 370 | ) ?[*]f32; 371 | 372 | pub extern fn stbi_load_from_memory( 373 | buffer: [*]const u8, 374 | len: c_int, 375 | x: *c_int, 376 | y: *c_int, 377 | channels_in_file: *c_int, 378 | desired_channels: c_int, 379 | ) ?[*]u8; 380 | 381 | extern fn stbi_image_free(image_data: ?[*]u8) void; 382 | 383 | extern fn stbi_hdr_to_ldr_scale(scale: f32) void; 384 | extern fn stbi_hdr_to_ldr_gamma(gamma: f32) void; 385 | extern fn stbi_ldr_to_hdr_scale(scale: f32) void; 386 | extern fn stbi_ldr_to_hdr_gamma(gamma: f32) void; 387 | 388 | extern fn stbi_is_16_bit(filename: [*:0]const u8) c_int; 389 | extern fn stbi_is_hdr(filename: [*:0]const u8) c_int; 390 | 391 | extern fn stbi_set_flip_vertically_on_load(flag_true_if_should_flip: c_int) void; 392 | 393 | extern fn stbir_resize_uint8( 394 | input_pixels: [*]const u8, 395 | input_w: c_int, 396 | input_h: c_int, 397 | input_stride_in_bytes: c_int, 398 | output_pixels: [*]u8, 399 | output_w: c_int, 400 | output_h: c_int, 401 | output_stride_in_bytes: c_int, 402 | num_channels: c_int, 403 | ) void; 404 | 405 | extern fn stbi_write_jpg( 406 | filename: [*:0]const u8, 407 | w: c_int, 408 | h: c_int, 409 | comp: c_int, 410 | data: [*]const u8, 411 | quality: c_int, 412 | ) c_int; 413 | extern fn stbi_write_png( 414 | filename: [*:0]const u8, 415 | w: c_int, 416 | h: c_int, 417 | comp: c_int, 418 | data: [*]const u8, 419 | stride_in_bytes: c_int, 420 | ) c_int; 421 | 422 | test "zstbi.basic" { 423 | init(std.testing.allocator); 424 | defer deinit(); 425 | } 426 | -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "content/"; 10 | 11 | pub fn build(b: *std.build.Builder, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "basic_lighting", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/basic_lighting/content/awesomeface.png -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/content/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/basic_lighting/content/container.jpg -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/content/light_shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | 4 | out vec4 FragColor; 5 | 6 | void main() 7 | { 8 | FragColor = vec4(1.0f); 9 | }; 10 | -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/content/light_shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | 4 | uniform mat4 model; 5 | uniform mat4 view; 6 | uniform mat4 projection; 7 | 8 | void main() 9 | { 10 | gl_Position = projection * view * model * vec4(aPos, 1.0f); 11 | } -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/content/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 FragColor; 4 | 5 | in vec3 FragPos; 6 | in vec3 Normal; 7 | 8 | uniform vec3 lightPos; 9 | uniform vec3 objectColor; 10 | uniform vec3 lightColor; 11 | uniform vec3 viewPos; 12 | 13 | void main() 14 | { 15 | float ambientStrength = 0.1; 16 | vec3 ambient = ambientStrength * lightColor; 17 | 18 | float specularStrength = 0.75; 19 | 20 | vec3 norm = normalize(Normal); 21 | vec3 lightDir = normalize(lightPos - FragPos); 22 | float diff = max(dot(norm, lightDir), 0.0); 23 | vec3 diffuse = diff * lightColor; 24 | 25 | vec3 viewDir = normalize(viewPos - FragPos); 26 | vec3 reflectDir = reflect(-lightDir, norm); 27 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), 256); 28 | vec3 specular = specularStrength * spec * lightColor; 29 | 30 | vec3 result = (ambient + diffuse + specular) * objectColor; 31 | 32 | FragColor = vec4(result, 1.0f); 33 | }; 34 | -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/content/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | 5 | uniform mat4 model; 6 | uniform mat4 view; 7 | uniform mat4 projection; 8 | 9 | out vec3 FragPos; 10 | out vec3 Normal; 11 | 12 | void main() 13 | { 14 | gl_Position = projection * view * vec4(aPos, 1.0f); 15 | FragPos = vec3(vec4(aPos, 1.0)); 16 | Normal = aNormal; 17 | } -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/basic_lighting/image.png -------------------------------------------------------------------------------- /src/getting_started/basic_lighting/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | const glfw = @import("glfw"); 4 | const zstbi = @import("zstbi"); 5 | const zm = @import("zmath"); 6 | const gl = @import("gl"); 7 | const Shader = @import("Shader"); 8 | const Camera = @import("Camera"); 9 | const common = @import("common"); 10 | 11 | // Camera 12 | const camera_pos = zm.loadArr3(.{ 0.0, 0.0, 5.0 }); 13 | var lastX: f64 = 0.0; 14 | var lastY: f64 = 0.0; 15 | var first_mouse = true; 16 | var camera = Camera.camera(camera_pos); 17 | 18 | // Timing 19 | var delta_time: f32 = 0.0; 20 | var last_frame: f32 = 0.0; 21 | 22 | // lighting 23 | var light_position = [_]f32{ 10.0, 10.0, 10.0 }; 24 | 25 | const WindowSize = struct { 26 | pub const width: u32 = 800; 27 | pub const height: u32 = 600; 28 | }; 29 | 30 | pub fn main() !void { 31 | 32 | // glfw: initialize and configure 33 | // ------------------------------ 34 | if (!glfw.init(.{})) { 35 | std.log.err("GLFW initialization failed", .{}); 36 | return; 37 | } 38 | defer glfw.terminate(); 39 | 40 | // glfw window creation 41 | // -------------------- 42 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 43 | .opengl_profile = .opengl_core_profile, 44 | .context_version_major = 4, 45 | .context_version_minor = 1, 46 | }) orelse { 47 | std.log.err("GLFW Window creation failed", .{}); 48 | return; 49 | }; 50 | defer window.destroy(); 51 | 52 | glfw.makeContextCurrent(window); 53 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 54 | // Capture mouse, disable cursor visibility 55 | glfw.Window.setInputMode(window, glfw.Window.InputMode.cursor, glfw.Window.InputModeCursor.disabled); 56 | glfw.Window.setCursorPosCallback(window, mouseCallback); 57 | glfw.Window.setScrollCallback(window, mouseScrollCallback); 58 | 59 | // Load all OpenGL function pointers 60 | // --------------------------------------- 61 | const proc: glfw.GLProc = undefined; 62 | try gl.load(proc, glGetProcAddress); 63 | 64 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 65 | var allocator = gpa.allocator(); 66 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 67 | defer arena_allocator_state.deinit(); 68 | const arena = arena_allocator_state.allocator(); 69 | 70 | // Enable OpenGL depth testing (use Z-buffer information) 71 | gl.enable(gl.DEPTH_TEST); 72 | 73 | // create shader program 74 | var shader_program: Shader = Shader.create(arena, "content/shader.vs", "content/shader.fs"); 75 | var light_shader: Shader = Shader.create(arena, "content/light_shader.vs", "content/light_shader.fs"); 76 | 77 | // set up vertex data (and buffer(s)) and configure vertex attributes 78 | // ------------------------------------------------------------------ 79 | 80 | const vertices_2D = [_]f32{ 81 | // positions // colors // texture coords 82 | 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right 83 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right 84 | -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left 85 | -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top left 86 | }; 87 | 88 | _ = vertices_2D; 89 | 90 | const vertices_3D = [_]f32{ 91 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 92 | 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 93 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 94 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 95 | -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 96 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 97 | 98 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 99 | 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 100 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 101 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 102 | -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 103 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 104 | 105 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 106 | -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 107 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 108 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 109 | -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 110 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 111 | 112 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 113 | 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 114 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 115 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 116 | 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 117 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 118 | 119 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 120 | 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 121 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 122 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 123 | -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 124 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 125 | 126 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 127 | 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 128 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 129 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 130 | -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 131 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 132 | }; 133 | 134 | var VBO: c_uint = undefined; 135 | var VAO: c_uint = undefined; 136 | var light_VAO: c_uint = undefined; 137 | 138 | gl.genVertexArrays(1, &VAO); 139 | defer gl.deleteVertexArrays(1, &VAO); 140 | 141 | gl.genVertexArrays(1, &light_VAO); 142 | defer gl.deleteVertexArrays(1, &light_VAO); 143 | 144 | gl.genBuffers(1, &VBO); 145 | defer gl.deleteBuffers(1, &VBO); 146 | 147 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 148 | gl.bindVertexArray(VAO); 149 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 150 | // Fill our buffer with the vertex data 151 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices_3D.len, &vertices_3D, gl.STATIC_DRAW); 152 | 153 | // vertex 154 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 6 * @sizeOf(f32), null); 155 | gl.enableVertexAttribArray(0); 156 | 157 | // normal attribute 158 | const normal_offset: [*c]c_uint = (3 * @sizeOf(f32)); 159 | gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 6 * @sizeOf(f32), normal_offset); 160 | gl.enableVertexAttribArray(1); 161 | 162 | // Configure light VAO 163 | gl.bindVertexArray(light_VAO); 164 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 165 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 6 * @sizeOf(f32), null); 166 | gl.enableVertexAttribArray(0); 167 | 168 | // View matrix 169 | var view: [16]f32 = undefined; 170 | 171 | // Buffer to store Orojection matrix (in render loop) 172 | var proj: [16]f32 = undefined; 173 | 174 | var light_model: [16]f32 = undefined; 175 | 176 | while (!window.shouldClose()) { 177 | 178 | // Time per frame 179 | const current_frame = @floatCast(f32, glfw.getTime()); 180 | delta_time = current_frame - last_frame; 181 | last_frame = current_frame; 182 | 183 | processInput(window); 184 | 185 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 186 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 187 | 188 | light_position[0] = 1.0 + zm.sin(@floatCast(f32, glfw.getTime())) * 2.0; 189 | light_position[1] = 1.0 + zm.sin(@floatCast(f32, glfw.getTime()) / 2.0) * 1.0; 190 | 191 | shader_program.use(); 192 | shader_program.setVec3f("objectColor", .{ 1.0, 0.5, 0.31 }); 193 | shader_program.setVec3f("lightColor", .{ 1.0, 1.0, 1.0 }); 194 | shader_program.setVec3f("lightPos", light_position); 195 | shader_program.setVec3f("viewPos", zm.vecToArr3(camera.position)); 196 | 197 | // Projection matrix 198 | const projM = x: { 199 | const window_size = window.getSize(); 200 | const aspect = @intToFloat(f32, window_size.width) / @intToFloat(f32, window_size.height); 201 | var projM = zm.perspectiveFovRhGl(camera.zoom * common.RAD_CONVERSION, aspect, 0.1, 100.0); 202 | break :x projM; 203 | }; 204 | zm.storeMat(&proj, projM); 205 | shader_program.setMat4f("projection", proj); 206 | 207 | // View matrix: Camera 208 | const viewM = camera.getViewMatrix(); 209 | zm.storeMat(&view, viewM); 210 | shader_program.setMat4f("view", view); 211 | 212 | gl.bindVertexArray(VAO); 213 | gl.drawArrays(gl.TRIANGLES, 0, 36); 214 | 215 | const light_trans = zm.translation(light_position[0], light_position[1], light_position[2]); 216 | const light_modelM = zm.mul(light_trans, zm.scaling(0.2, 0.2, 0.2)); 217 | zm.storeMat(&light_model, light_modelM); 218 | 219 | light_shader.use(); 220 | light_shader.setMat4f("projection", proj); 221 | light_shader.setMat4f("view", view); 222 | light_shader.setMat4f("model", light_model); 223 | gl.bindVertexArray(light_VAO); 224 | gl.drawArrays(gl.TRIANGLES, 0, 36); 225 | 226 | window.swapBuffers(); 227 | glfw.pollEvents(); 228 | } 229 | } 230 | 231 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 232 | _ = p; 233 | return glfw.getProcAddress(proc); 234 | } 235 | 236 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 237 | _ = window; 238 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 239 | } 240 | 241 | fn processInput(window: glfw.Window) void { 242 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 243 | _ = glfw.Window.setShouldClose(window, true); 244 | } 245 | 246 | if (glfw.Window.getKey(window, glfw.Key.w) == glfw.Action.press) { 247 | camera.processKeyboard(Camera.CameraMovement.FORWARD, delta_time); 248 | } 249 | if (glfw.Window.getKey(window, glfw.Key.s) == glfw.Action.press) { 250 | camera.processKeyboard(Camera.CameraMovement.BACKWARD, delta_time); 251 | } 252 | if (glfw.Window.getKey(window, glfw.Key.a) == glfw.Action.press) { 253 | camera.processKeyboard(Camera.CameraMovement.LEFT, delta_time); 254 | } 255 | if (glfw.Window.getKey(window, glfw.Key.d) == glfw.Action.press) { 256 | camera.processKeyboard(Camera.CameraMovement.RIGHT, delta_time); 257 | } 258 | } 259 | 260 | fn mouseCallback(window: glfw.Window, xpos: f64, ypos: f64) void { 261 | _ = window; 262 | 263 | if (first_mouse) { 264 | lastX = xpos; 265 | lastY = ypos; 266 | first_mouse = false; 267 | } 268 | 269 | var xoffset = xpos - lastX; 270 | var yoffset = ypos - lastY; 271 | 272 | lastX = xpos; 273 | lastY = ypos; 274 | 275 | camera.processMouseMovement(xoffset, yoffset, true); 276 | } 277 | 278 | fn mouseScrollCallback(window: glfw.Window, xoffset: f64, yoffset: f64) void { 279 | _ = window; 280 | _ = xoffset; 281 | 282 | camera.processMouseScroll(yoffset); 283 | } 284 | -------------------------------------------------------------------------------- /src/getting_started/camera_rotate/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "content/"; 10 | 11 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "camera_rotate", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/camera_rotate/content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/camera_rotate/content/awesomeface.png -------------------------------------------------------------------------------- /src/getting_started/camera_rotate/content/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/camera_rotate/content/container.jpg -------------------------------------------------------------------------------- /src/getting_started/camera_rotate/content/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 FragColor; 4 | in vec2 texCoord; 5 | 6 | uniform sampler2D texture1; 7 | uniform sampler2D texture2; 8 | 9 | void main() 10 | { 11 | FragColor = mix(texture(texture1, texCoord),texture(texture2, texCoord), 0.5f); 12 | }; 13 | -------------------------------------------------------------------------------- /src/getting_started/camera_rotate/content/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec2 aTexCoord; 4 | 5 | out vec2 texCoord; 6 | 7 | uniform mat4 model; 8 | uniform mat4 view; 9 | uniform mat4 projection; 10 | 11 | void main() 12 | { 13 | gl_Position = projection * view * model * vec4(aPos, 1.0f); 14 | texCoord = aTexCoord; 15 | } -------------------------------------------------------------------------------- /src/getting_started/camera_rotate/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | const glfw = @import("glfw"); 4 | const zstbi = @import("zstbi"); 5 | const zm = @import("zmath"); 6 | const gl = @import("gl"); 7 | const Shader = @import("Shader"); 8 | const common = @import("common"); 9 | 10 | const WindowSize = struct { 11 | pub const width: u32 = 800; 12 | pub const height: u32 = 600; 13 | }; 14 | 15 | pub fn main() !void { 16 | 17 | // glfw: initialize and configure 18 | // ------------------------------ 19 | if (!glfw.init(.{})) { 20 | std.log.err("GLFW initialization failed", .{}); 21 | return; 22 | } 23 | defer glfw.terminate(); 24 | 25 | // glfw window creation 26 | // -------------------- 27 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 28 | .opengl_profile = .opengl_core_profile, 29 | .context_version_major = 4, 30 | .context_version_minor = 1, 31 | }) orelse { 32 | std.log.err("GLFW Window creation failed", .{}); 33 | return; 34 | }; 35 | defer window.destroy(); 36 | 37 | glfw.makeContextCurrent(window); 38 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 39 | 40 | // Load all OpenGL function pointers 41 | // --------------------------------------- 42 | const proc: glfw.GLProc = undefined; 43 | try gl.load(proc, glGetProcAddress); 44 | 45 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 46 | var allocator = gpa.allocator(); 47 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 48 | defer arena_allocator_state.deinit(); 49 | const arena = arena_allocator_state.allocator(); 50 | 51 | // create shader program 52 | var shader_program: Shader = Shader.create(arena, "content/shader.vs", "content/shader.fs"); 53 | 54 | // set up vertex data (and buffer(s)) and configure vertex attributes 55 | // ------------------------------------------------------------------ 56 | 57 | const vertices_2D = [_]f32{ 58 | // positions // colors // texture coords 59 | 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right 60 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right 61 | -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left 62 | -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top left 63 | }; 64 | 65 | _ = vertices_2D; 66 | 67 | const vertices_3D = [_]f32{ 68 | -0.5, -0.5, -0.5, 0.0, 0.0, 69 | 0.5, -0.5, -0.5, 1.0, 0.0, 70 | 0.5, 0.5, -0.5, 1.0, 1.0, 71 | 0.5, 0.5, -0.5, 1.0, 1.0, 72 | -0.5, 0.5, -0.5, 0.0, 1.0, 73 | -0.5, -0.5, -0.5, 0.0, 0.0, 74 | 75 | -0.5, -0.5, 0.5, 0.0, 0.0, 76 | 0.5, -0.5, 0.5, 1.0, 0.0, 77 | 0.5, 0.5, 0.5, 1.0, 1.0, 78 | 0.5, 0.5, 0.5, 1.0, 1.0, 79 | -0.5, 0.5, 0.5, 0.0, 1.0, 80 | -0.5, -0.5, 0.5, 0.0, 0.0, 81 | 82 | -0.5, 0.5, 0.5, 1.0, 0.0, 83 | -0.5, 0.5, -0.5, 1.0, 1.0, 84 | -0.5, -0.5, -0.5, 0.0, 1.0, 85 | -0.5, -0.5, -0.5, 0.0, 1.0, 86 | -0.5, -0.5, 0.5, 0.0, 0.0, 87 | -0.5, 0.5, 0.5, 1.0, 0.0, 88 | 89 | 0.5, 0.5, 0.5, 1.0, 0.0, 90 | 0.5, 0.5, -0.5, 1.0, 1.0, 91 | 0.5, -0.5, -0.5, 0.0, 1.0, 92 | 0.5, -0.5, -0.5, 0.0, 1.0, 93 | 0.5, -0.5, 0.5, 0.0, 0.0, 94 | 0.5, 0.5, 0.5, 1.0, 0.0, 95 | 96 | -0.5, -0.5, -0.5, 0.0, 1.0, 97 | 0.5, -0.5, -0.5, 1.0, 1.0, 98 | 0.5, -0.5, 0.5, 1.0, 0.0, 99 | 0.5, -0.5, 0.5, 1.0, 0.0, 100 | -0.5, -0.5, 0.5, 0.0, 0.0, 101 | -0.5, -0.5, -0.5, 0.0, 1.0, 102 | 103 | -0.5, 0.5, -0.5, 0.0, 1.0, 104 | 0.5, 0.5, -0.5, 1.0, 1.0, 105 | 0.5, 0.5, 0.5, 1.0, 0.0, 106 | 0.5, 0.5, 0.5, 1.0, 0.0, 107 | -0.5, 0.5, 0.5, 0.0, 0.0, 108 | -0.5, 0.5, -0.5, 0.0, 1.0, 109 | }; 110 | 111 | const cube_positions = [_][3]f32{ 112 | .{ 0.0, 0.0, 0.0 }, 113 | .{ 2.0, 5.0, -15.0 }, 114 | .{ -1.5, -2.2, -2.5 }, 115 | .{ -3.8, -2.0, -12.3 }, 116 | .{ 2.4, -0.4, -3.5 }, 117 | .{ -1.7, 3.0, -7.5 }, 118 | .{ 1.3, -2.0, -2.5 }, 119 | .{ 1.5, 2.0, -2.5 }, 120 | .{ 1.5, 0.2, -1.5 }, 121 | .{ -1.3, 1.0, -1.5 }, 122 | }; 123 | 124 | var VBO: c_uint = undefined; 125 | var VAO: c_uint = undefined; 126 | 127 | gl.genVertexArrays(1, &VAO); 128 | defer gl.deleteVertexArrays(1, &VAO); 129 | 130 | gl.genBuffers(1, &VBO); 131 | defer gl.deleteBuffers(1, &VBO); 132 | 133 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 134 | gl.bindVertexArray(VAO); 135 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 136 | // Fill our buffer with the vertex data 137 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices_3D.len, &vertices_3D, gl.STATIC_DRAW); 138 | 139 | // vertex 140 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 5 * @sizeOf(f32), null); 141 | gl.enableVertexAttribArray(0); 142 | 143 | // texture coords 144 | const tex_offset: [*c]c_uint = (3 * @sizeOf(f32)); 145 | gl.vertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 5 * @sizeOf(f32), tex_offset); 146 | gl.enableVertexAttribArray(1); 147 | 148 | // zstbi: loading an image. 149 | zstbi.init(allocator); 150 | defer zstbi.deinit(); 151 | 152 | const image1_path = common.pathToContent(arena, "content/container.jpg") catch unreachable; 153 | var image1 = try zstbi.Image.init(&image1_path, 0); 154 | defer image1.deinit(); 155 | std.debug.print( 156 | "\nImage 1 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", 157 | .{ image1.width, image1.height, image1.num_components }, 158 | ); 159 | 160 | zstbi.setFlipVerticallyOnLoad(true); 161 | const image2_path = common.pathToContent(arena, "content/awesomeface.png") catch unreachable; 162 | var image2 = try zstbi.Image.init(&image2_path, 0); 163 | defer image2.deinit(); 164 | std.debug.print( 165 | "\nImage 2 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", 166 | .{ image2.width, image2.height, image2.num_components }, 167 | ); 168 | 169 | // Create and bind texture1 resource 170 | var texture1: c_uint = undefined; 171 | 172 | gl.genTextures(1, &texture1); 173 | gl.activeTexture(gl.TEXTURE0); // activate the texture unit first before binding texture 174 | gl.bindTexture(gl.TEXTURE_2D, texture1); 175 | 176 | // set the texture1 wrapping parameters 177 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 178 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 179 | // set texture1 filtering parameters 180 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 181 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 182 | 183 | // Generate the texture1 184 | gl.texImage2D( 185 | gl.TEXTURE_2D, 186 | 0, 187 | gl.RGB, 188 | @intCast(c_int, image1.width), 189 | @intCast(c_int, image1.height), 190 | 0, 191 | gl.RGB, 192 | gl.UNSIGNED_BYTE, 193 | @ptrCast([*c]const u8, image1.data), 194 | ); 195 | gl.generateMipmap(gl.TEXTURE_2D); 196 | 197 | // Texture2 198 | var texture2: c_uint = undefined; 199 | 200 | gl.genTextures(1, &texture2); 201 | gl.activeTexture(gl.TEXTURE1); // activate the texture unit first before binding texture 202 | gl.bindTexture(gl.TEXTURE_2D, texture2); 203 | 204 | // set the texture1 wrapping parameters 205 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 206 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 207 | // set texture1 filtering parameters 208 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 209 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 210 | 211 | // Generate the texture1 212 | gl.texImage2D( 213 | gl.TEXTURE_2D, 214 | 0, 215 | gl.RGB, 216 | @intCast(c_int, image2.width), 217 | @intCast(c_int, image2.height), 218 | 0, 219 | gl.RGBA, 220 | gl.UNSIGNED_BYTE, 221 | @ptrCast([*c]const u8, image2.data), 222 | ); 223 | gl.generateMipmap(gl.TEXTURE_2D); 224 | 225 | // Enable OpenGL depth testing (use Z-buffer information) 226 | gl.enable(gl.DEPTH_TEST); 227 | 228 | shader_program.use(); 229 | shader_program.setInt("texture1", 0); 230 | shader_program.setInt("texture2", 1); 231 | 232 | // Create the transformation matrices: 233 | // Degree to radians conversion factor 234 | const rad_conversion = math.pi / 180.0; 235 | 236 | // Buffer to store Model matrix 237 | var model: [16]f32 = undefined; 238 | 239 | // View matrix 240 | var view: [16]f32 = undefined; 241 | 242 | // Buffer to store Orojection matrix (in render loop) 243 | var proj: [16]f32 = undefined; 244 | 245 | while (!window.shouldClose()) { 246 | processInput(window); 247 | 248 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 249 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 250 | gl.activeTexture(gl.TEXTURE0); 251 | gl.bindTexture(gl.TEXTURE_2D, texture1); 252 | gl.activeTexture(gl.TEXTURE1); 253 | gl.bindTexture(gl.TEXTURE_2D, texture2); 254 | gl.bindVertexArray(VAO); 255 | 256 | // Projection matrix 257 | const projM = x: { 258 | var window_size = window.getSize(); 259 | var fov = @intToFloat(f32, window_size.width) / @intToFloat(f32, window_size.height); 260 | var projM = zm.perspectiveFovRhGl(45.0 * rad_conversion, fov, 0.1, 100.0); 261 | break :x projM; 262 | }; 263 | zm.storeMat(&proj, projM); 264 | shader_program.setMat4f("projection", proj); 265 | 266 | // View matrix: Camera 267 | const radius: f32 = 10.0; 268 | const camX: f32 = @floatCast(f32, @sin(glfw.getTime())) * radius; 269 | const camZ: f32 = @floatCast(f32, @cos(glfw.getTime())) * radius; 270 | const viewM = zm.lookAtRh( 271 | zm.loadArr3(.{ camX, 0.0, camZ }), 272 | zm.loadArr3(.{ 0.0, 0.0, 0.0 }), 273 | zm.loadArr3(.{ 0.0, 1.0, 0.0 }), 274 | ); 275 | zm.storeMat(&view, viewM); 276 | shader_program.setMat4f("view", view); 277 | 278 | for (cube_positions, 0..) |cube_position, i| { 279 | // Model matrix 280 | const cube_trans = zm.translation(cube_position[0], cube_position[1], cube_position[2]); 281 | const rotation_direction = (((@mod(@intToFloat(f32, i + 1), 2.0)) * 2.0) - 1.0); 282 | const cube_rot = zm.matFromAxisAngle( 283 | zm.f32x4(1.0, 0.3, 0.5, 1.0), 284 | @floatCast(f32, glfw.getTime()) * 55.0 * rotation_direction * rad_conversion, 285 | ); 286 | const modelM = zm.mul(cube_rot, cube_trans); 287 | zm.storeMat(&model, modelM); 288 | shader_program.setMat4f("model", model); 289 | 290 | gl.drawArrays(gl.TRIANGLES, 0, 36); 291 | } 292 | 293 | window.swapBuffers(); 294 | glfw.pollEvents(); 295 | } 296 | } 297 | 298 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 299 | _ = p; 300 | return glfw.getProcAddress(proc); 301 | } 302 | 303 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 304 | _ = window; 305 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 306 | } 307 | 308 | fn processInput(window: glfw.Window) void { 309 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 310 | _ = glfw.Window.setShouldClose(window, true); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "content/"; 10 | 11 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "coordinate_system", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/coordinate_systems/content/awesomeface.png -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/content/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/coordinate_systems/content/container.jpg -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/content/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 FragColor; 4 | in vec2 texCoord; 5 | 6 | uniform sampler2D texture1; 7 | uniform sampler2D texture2; 8 | 9 | void main() 10 | { 11 | FragColor = mix(texture(texture1, texCoord),texture(texture2, texCoord), 0.5f); 12 | }; 13 | -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/content/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec2 aTexCoord; 4 | 5 | out vec2 texCoord; 6 | 7 | uniform mat4 model; 8 | uniform mat4 view; 9 | uniform mat4 projection; 10 | 11 | void main() 12 | { 13 | gl_Position = projection * view * model * vec4(aPos, 1.0f); 14 | texCoord = aTexCoord; 15 | } -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/coordinate_systems/image.png -------------------------------------------------------------------------------- /src/getting_started/coordinate_systems/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | const glfw = @import("glfw"); 4 | const zstbi = @import("zstbi"); 5 | const zm = @import("zmath"); 6 | const gl = @import("gl"); 7 | const Shader = @import("Shader"); 8 | const common = @import("common"); 9 | 10 | const WindowSize = struct { 11 | pub const width: u32 = 800; 12 | pub const height: u32 = 600; 13 | }; 14 | 15 | pub fn main() !void { 16 | 17 | // glfw: initialize and configure 18 | // ------------------------------ 19 | if (!glfw.init(.{})) { 20 | std.log.err("GLFW initialization failed", .{}); 21 | return; 22 | } 23 | defer glfw.terminate(); 24 | 25 | // glfw window creation 26 | // -------------------- 27 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 28 | .opengl_profile = .opengl_core_profile, 29 | .context_version_major = 4, 30 | .context_version_minor = 1, 31 | }) orelse { 32 | std.log.err("GLFW Window creation failed", .{}); 33 | return; 34 | }; 35 | defer window.destroy(); 36 | 37 | glfw.makeContextCurrent(window); 38 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 39 | 40 | // Load all OpenGL function pointers 41 | // --------------------------------------- 42 | const proc: glfw.GLProc = undefined; 43 | try gl.load(proc, glGetProcAddress); 44 | 45 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 46 | var allocator = gpa.allocator(); 47 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 48 | defer arena_allocator_state.deinit(); 49 | const arena = arena_allocator_state.allocator(); 50 | 51 | // create shader program 52 | var shader_program: Shader = Shader.create(arena, "content/shader.vs", "content/shader.fs"); 53 | 54 | // set up vertex data (and buffer(s)) and configure vertex attributes 55 | // ------------------------------------------------------------------ 56 | 57 | const vertices_2D = [_]f32{ 58 | // positions // colors // texture coords 59 | 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right 60 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right 61 | -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left 62 | -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top left 63 | }; 64 | 65 | _ = vertices_2D; 66 | 67 | const vertices_3D = [_]f32{ 68 | -0.5, -0.5, -0.5, 0.0, 0.0, 69 | 0.5, -0.5, -0.5, 1.0, 0.0, 70 | 0.5, 0.5, -0.5, 1.0, 1.0, 71 | 0.5, 0.5, -0.5, 1.0, 1.0, 72 | -0.5, 0.5, -0.5, 0.0, 1.0, 73 | -0.5, -0.5, -0.5, 0.0, 0.0, 74 | 75 | -0.5, -0.5, 0.5, 0.0, 0.0, 76 | 0.5, -0.5, 0.5, 1.0, 0.0, 77 | 0.5, 0.5, 0.5, 1.0, 1.0, 78 | 0.5, 0.5, 0.5, 1.0, 1.0, 79 | -0.5, 0.5, 0.5, 0.0, 1.0, 80 | -0.5, -0.5, 0.5, 0.0, 0.0, 81 | 82 | -0.5, 0.5, 0.5, 1.0, 0.0, 83 | -0.5, 0.5, -0.5, 1.0, 1.0, 84 | -0.5, -0.5, -0.5, 0.0, 1.0, 85 | -0.5, -0.5, -0.5, 0.0, 1.0, 86 | -0.5, -0.5, 0.5, 0.0, 0.0, 87 | -0.5, 0.5, 0.5, 1.0, 0.0, 88 | 89 | 0.5, 0.5, 0.5, 1.0, 0.0, 90 | 0.5, 0.5, -0.5, 1.0, 1.0, 91 | 0.5, -0.5, -0.5, 0.0, 1.0, 92 | 0.5, -0.5, -0.5, 0.0, 1.0, 93 | 0.5, -0.5, 0.5, 0.0, 0.0, 94 | 0.5, 0.5, 0.5, 1.0, 0.0, 95 | 96 | -0.5, -0.5, -0.5, 0.0, 1.0, 97 | 0.5, -0.5, -0.5, 1.0, 1.0, 98 | 0.5, -0.5, 0.5, 1.0, 0.0, 99 | 0.5, -0.5, 0.5, 1.0, 0.0, 100 | -0.5, -0.5, 0.5, 0.0, 0.0, 101 | -0.5, -0.5, -0.5, 0.0, 1.0, 102 | 103 | -0.5, 0.5, -0.5, 0.0, 1.0, 104 | 0.5, 0.5, -0.5, 1.0, 1.0, 105 | 0.5, 0.5, 0.5, 1.0, 0.0, 106 | 0.5, 0.5, 0.5, 1.0, 0.0, 107 | -0.5, 0.5, 0.5, 0.0, 0.0, 108 | -0.5, 0.5, -0.5, 0.0, 1.0, 109 | }; 110 | 111 | const cube_positions = [_][3]f32{ 112 | .{ 0.0, 0.0, 0.0 }, 113 | .{ 2.0, 5.0, -15.0 }, 114 | .{ -1.5, -2.2, -2.5 }, 115 | .{ -3.8, -2.0, -12.3 }, 116 | .{ 2.4, -0.4, -3.5 }, 117 | .{ -1.7, 3.0, -7.5 }, 118 | .{ 1.3, -2.0, -2.5 }, 119 | .{ 1.5, 2.0, -2.5 }, 120 | .{ 1.5, 0.2, -1.5 }, 121 | .{ -1.3, 1.0, -1.5 }, 122 | }; 123 | 124 | var VBO: c_uint = undefined; 125 | var VAO: c_uint = undefined; 126 | 127 | gl.genVertexArrays(1, &VAO); 128 | defer gl.deleteVertexArrays(1, &VAO); 129 | 130 | gl.genBuffers(1, &VBO); 131 | defer gl.deleteBuffers(1, &VBO); 132 | 133 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 134 | gl.bindVertexArray(VAO); 135 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 136 | // Fill our buffer with the vertex data 137 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices_3D.len, &vertices_3D, gl.STATIC_DRAW); 138 | 139 | // vertex 140 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 5 * @sizeOf(f32), null); 141 | gl.enableVertexAttribArray(0); 142 | 143 | // texture coords 144 | const tex_offset: [*c]c_uint = (3 * @sizeOf(f32)); 145 | gl.vertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 5 * @sizeOf(f32), tex_offset); 146 | gl.enableVertexAttribArray(1); 147 | 148 | // zstbi: loading an image. 149 | zstbi.init(allocator); 150 | defer zstbi.deinit(); 151 | 152 | const image1_path = common.pathToContent(arena, "content/container.jpg") catch unreachable; 153 | var image1 = try zstbi.Image.init(&image1_path, 0); 154 | defer image1.deinit(); 155 | std.debug.print("\nImage 1 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", .{ image1.width, image1.height, image1.num_components }); 156 | 157 | zstbi.setFlipVerticallyOnLoad(true); 158 | const image2_path = common.pathToContent(arena, "content/awesomeface.png") catch unreachable; 159 | var image2 = try zstbi.Image.init(&image2_path, 0); 160 | defer image2.deinit(); 161 | std.debug.print("\nImage 2 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", .{ image2.width, image2.height, image2.num_components }); 162 | 163 | // Create and bind texture1 resource 164 | var texture1: c_uint = undefined; 165 | 166 | gl.genTextures(1, &texture1); 167 | gl.activeTexture(gl.TEXTURE0); // activate the texture unit first before binding texture 168 | gl.bindTexture(gl.TEXTURE_2D, texture1); 169 | 170 | // set the texture1 wrapping parameters 171 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 172 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 173 | // set texture1 filtering parameters 174 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 175 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 176 | 177 | // Generate the texture1 178 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image1.width), @intCast(c_int, image1.height), 0, gl.RGB, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image1.data)); 179 | gl.generateMipmap(gl.TEXTURE_2D); 180 | 181 | // Texture2 182 | var texture2: c_uint = undefined; 183 | 184 | gl.genTextures(1, &texture2); 185 | gl.activeTexture(gl.TEXTURE1); // activate the texture unit first before binding texture 186 | gl.bindTexture(gl.TEXTURE_2D, texture2); 187 | 188 | // set the texture1 wrapping parameters 189 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 190 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 191 | // set texture1 filtering parameters 192 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 193 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 194 | 195 | // Generate the texture1 196 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image2.width), @intCast(c_int, image2.height), 0, gl.RGBA, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image2.data)); 197 | gl.generateMipmap(gl.TEXTURE_2D); 198 | 199 | // Enable OpenGL depth testing (use Z-buffer information) 200 | gl.enable(gl.DEPTH_TEST); 201 | 202 | shader_program.use(); 203 | shader_program.setInt("texture1", 0); 204 | shader_program.setInt("texture2", 1); 205 | 206 | // Create the transformation matrices: 207 | // Degree to radians conversion factor 208 | const rad_conversion = math.pi / 180.0; 209 | 210 | // Buffer to store Model matrix 211 | var model: [16]f32 = undefined; 212 | 213 | // View matrix 214 | const viewM = zm.translation(0.0, 0.0, -5.0); 215 | var view: [16]f32 = undefined; 216 | zm.storeMat(&view, viewM); 217 | shader_program.setMat4f("view", view); 218 | 219 | // Buffer to store Orojection matrix (in render loop) 220 | var proj: [16]f32 = undefined; 221 | 222 | while (!window.shouldClose()) { 223 | processInput(window); 224 | 225 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 226 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 227 | gl.activeTexture(gl.TEXTURE0); 228 | gl.bindTexture(gl.TEXTURE_2D, texture1); 229 | gl.activeTexture(gl.TEXTURE1); 230 | gl.bindTexture(gl.TEXTURE_2D, texture2); 231 | gl.bindVertexArray(VAO); 232 | 233 | // Projection matrix 234 | const projM = x: { 235 | var window_size = window.getSize(); 236 | var fov = @intToFloat(f32, window_size.width) / @intToFloat(f32, window_size.height); 237 | var projM = zm.perspectiveFovRhGl(45.0 * rad_conversion, fov, 0.1, 100.0); 238 | break :x projM; 239 | }; 240 | zm.storeMat(&proj, projM); 241 | 242 | shader_program.setMat4f("projection", proj); 243 | 244 | for (cube_positions, 0..) |cube_position, i| { 245 | const cube_trans = zm.translation(cube_position[0], cube_position[1], cube_position[2]); 246 | const rotation_direction = (((@mod(@intToFloat(f32, i + 1), 2.0)) * 2.0) - 1.0); 247 | const cube_rot = zm.matFromAxisAngle( 248 | zm.f32x4(1.0, 0.3, 0.5, 1.0), 249 | @floatCast(f32, glfw.getTime()) * 55.0 * rotation_direction * rad_conversion, 250 | ); 251 | const modelM = zm.mul(cube_rot, cube_trans); 252 | zm.storeMat(&model, modelM); 253 | shader_program.setMat4f("model", model); 254 | gl.drawArrays(gl.TRIANGLES, 0, 36); 255 | } 256 | 257 | window.swapBuffers(); 258 | glfw.pollEvents(); 259 | } 260 | } 261 | 262 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 263 | _ = p; 264 | return glfw.getProcAddress(proc); 265 | } 266 | 267 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 268 | _ = window; 269 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 270 | } 271 | 272 | fn processInput(window: glfw.Window) void { 273 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 274 | _ = glfw.Window.setShouldClose(window, true); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/getting_started/hello_rectangle/README.md: -------------------------------------------------------------------------------- 1 | # Indexed drawing of a rectangle with Element Buffer Objects (EBO) aka (index buffer). 2 | 3 | From [Hello Triangle](https://learnopengl.com/Getting-started/Hello-Triangle) chapter: Element Buffer Objects section. 4 | 5 | ![wireframe rectangle](image.png) -------------------------------------------------------------------------------- /src/getting_started/hello_rectangle/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 10 | const exe = b.addExecutable(.{ 11 | .name = "hello_rectangle", 12 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 13 | .target = options.target, 14 | .optimize = options.build_mode, 15 | }); 16 | 17 | return exe; 18 | } 19 | -------------------------------------------------------------------------------- /src/getting_started/hello_rectangle/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/hello_rectangle/image.png -------------------------------------------------------------------------------- /src/getting_started/hello_rectangle/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const glfw = @import("glfw"); 3 | const gl = @import("gl"); 4 | 5 | const vertexShaderSource = 6 | \\ #version 410 core 7 | \\ layout (location = 0) in vec3 aPos; 8 | \\ void main() 9 | \\ { 10 | \\ gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 11 | \\ } 12 | ; 13 | 14 | const fragmentShaderSource = 15 | \\ #version 410 core 16 | \\ out vec4 FragColor; 17 | \\ void main() { 18 | \\ FragColor = vec4(1.0, 0.5, 0.2, 1.0); 19 | \\ } 20 | ; 21 | 22 | const WindowSize = struct { 23 | pub const width: u32 = 800; 24 | pub const height: u32 = 600; 25 | }; 26 | 27 | pub fn main() !void { 28 | 29 | // glfw: initialize and configure 30 | // ------------------------------ 31 | if (!glfw.init(.{})) { 32 | std.log.err("GLFW initialization failed", .{}); 33 | return; 34 | } 35 | defer glfw.terminate(); 36 | 37 | // glfw window creation 38 | // -------------------- 39 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 40 | .opengl_profile = .opengl_core_profile, 41 | .context_version_major = 4, 42 | .context_version_minor = 1, 43 | }) orelse { 44 | std.log.err("GLFW Window creation failed", .{}); 45 | return; 46 | }; 47 | defer window.destroy(); 48 | 49 | glfw.makeContextCurrent(window); 50 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 51 | 52 | // Load all OpenGL function pointers 53 | // --------------------------------------- 54 | const proc: glfw.GLProc = undefined; 55 | try gl.load(proc, glGetProcAddress); 56 | 57 | // Create vertex shader 58 | var vertexShader: c_uint = undefined; 59 | vertexShader = gl.createShader(gl.VERTEX_SHADER); 60 | defer gl.deleteShader(vertexShader); 61 | 62 | // Attach the shader source to the vertex shader object and compile it 63 | gl.shaderSource(vertexShader, 1, @ptrCast([*c]const [*c]const u8, &vertexShaderSource), 0); 64 | gl.compileShader(vertexShader); 65 | 66 | // Check if vertex shader was compiled successfully 67 | var success: c_int = undefined; 68 | var infoLog: [512]u8 = [_]u8{0} ** 512; 69 | 70 | gl.getShaderiv(vertexShader, gl.COMPILE_STATUS, &success); 71 | 72 | if (success == 0) { 73 | gl.getShaderInfoLog(vertexShader, 512, 0, &infoLog); 74 | std.log.err("{s}", .{infoLog}); 75 | } 76 | 77 | // Fragment shader 78 | var fragmentShader: c_uint = undefined; 79 | fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 80 | defer gl.deleteShader(fragmentShader); 81 | 82 | gl.shaderSource(fragmentShader, 1, @ptrCast([*c]const [*c]const u8, &fragmentShaderSource), 0); 83 | gl.compileShader(fragmentShader); 84 | 85 | gl.getShaderiv(fragmentShader, gl.COMPILE_STATUS, &success); 86 | 87 | if (success == 0) { 88 | gl.getShaderInfoLog(fragmentShader, 512, 0, &infoLog); 89 | std.log.err("{s}", .{infoLog}); 90 | } 91 | 92 | // create a program object 93 | var shaderProgram: c_uint = undefined; 94 | shaderProgram = gl.createProgram(); 95 | defer gl.deleteProgram(shaderProgram); 96 | 97 | // attach compiled shader objects to the program object and link 98 | gl.attachShader(shaderProgram, vertexShader); 99 | gl.attachShader(shaderProgram, fragmentShader); 100 | gl.linkProgram(shaderProgram); 101 | 102 | // check if shader linking was successfull 103 | gl.getProgramiv(shaderProgram, gl.LINK_STATUS, &success); 104 | if (success == 0) { 105 | gl.getProgramInfoLog(shaderProgram, 512, 0, &infoLog); 106 | std.log.err("{s}", .{infoLog}); 107 | } 108 | 109 | // set up vertex data (and buffer(s)) and configure vertex attributes 110 | // ------------------------------------------------------------------ 111 | // const vertices = [9]f32{ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0 }; 112 | const vertices = [12]f32{ 113 | 0.5, 0.5, 0.0, // top right 114 | 0.5, -0.5, 0.0, // bottom right 115 | -0.5, -0.5, 0.0, // bottom left 116 | -0.5, 0.5, 0.0, // top left 117 | }; 118 | 119 | const indices = [6]c_uint{ 120 | // note that we start from 0! 121 | 0, 1, 3, // first triangle 122 | 1, 2, 3, // second triangle 123 | }; 124 | 125 | var VBO: c_uint = undefined; 126 | var VAO: c_uint = undefined; 127 | var EBO: c_uint = undefined; 128 | 129 | gl.genVertexArrays(1, &VAO); 130 | defer gl.deleteVertexArrays(1, &VAO); 131 | 132 | gl.genBuffers(1, &VBO); 133 | defer gl.deleteBuffers(1, &VBO); 134 | 135 | gl.genBuffers(1, &EBO); 136 | defer gl.deleteBuffers(1, &EBO); 137 | 138 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 139 | gl.bindVertexArray(VAO); 140 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 141 | // Fill our buffer with the vertex data 142 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, gl.STATIC_DRAW); 143 | // copy our index array in an element buffer for OpenGL to use 144 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO); 145 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 6 * @sizeOf(c_uint), &indices, gl.STATIC_DRAW); 146 | 147 | // Specify and link our vertext attribute description 148 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 3 * @sizeOf(f32), null); 149 | gl.enableVertexAttribArray(0); 150 | 151 | // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind 152 | gl.bindBuffer(gl.ARRAY_BUFFER, 0); 153 | 154 | // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other 155 | // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. 156 | gl.bindVertexArray(0); 157 | 158 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0); 159 | 160 | // Wireframe mode 161 | gl.polygonMode(gl.FRONT_AND_BACK, gl.LINE); 162 | 163 | while (!window.shouldClose()) { 164 | processInput(window); 165 | 166 | gl.clearColor(0.2, 0.3, 0.3, 1.0); 167 | gl.clear(gl.COLOR_BUFFER_BIT); 168 | 169 | // Activate shaderProgram 170 | gl.useProgram(shaderProgram); 171 | gl.bindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized 172 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO); 173 | gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); 174 | gl.bindVertexArray(0); 175 | 176 | window.swapBuffers(); 177 | glfw.pollEvents(); 178 | } 179 | } 180 | 181 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 182 | _ = p; 183 | return glfw.getProcAddress(proc); 184 | } 185 | 186 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 187 | _ = window; 188 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 189 | } 190 | 191 | fn processInput(window: glfw.Window) void { 192 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 193 | _ = glfw.Window.setShouldClose(window, true); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/README.md: -------------------------------------------------------------------------------- 1 | # [Hello Triangle](https://learnopengl.com/Getting-started/Hello-Triangle) book chapter. 2 |
excercise 1 3 | 4 | Exercise 1: Try to draw 2 triangles next to each other using glDrawArrays by adding more vertices to your data 5 |
excercise 1 6 | 7 | Exercise 2: Now create the same 2 triangles using two different VAOs and VBOs for their data 8 |
excercise 1 9 | 10 | Exercise 3: Create two shader programs where the second program uses a different fragment shader that outputs the color yellow; draw both triangles again where one outputs the color yellow. 11 |
excercise 1 12 | 13 | ``` 14 | const std = @import("std"); 15 | const glfw = @import("glfw"); 16 | const gl = @import("gl"); 17 | 18 | const vertexShaderSource = 19 | \\ #version 410 core 20 | \\ layout (location = 0) in vec3 aPos; 21 | \\ void main() 22 | \\ { 23 | \\ gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 24 | \\ } 25 | ; 26 | 27 | const fragmentShaderSource_1 = 28 | \\ #version 410 core 29 | \\ out vec4 FragColor; 30 | \\ void main() { 31 | \\ FragColor = vec4(1.0, 0.5, 0.2, 1.0); 32 | \\ } 33 | ; 34 | 35 | const fragmentShaderSource_2 = 36 | \\ #version 410 core 37 | \\ out vec4 FragColor; 38 | \\ void main() { 39 | \\ FragColor = vec4(1.0, 1.0, 0.2, 1.0); 40 | \\ } 41 | ; 42 | 43 | const WindowSize = struct { 44 | pub const width: u32 = 800; 45 | pub const height: u32 = 600; 46 | }; 47 | 48 | pub fn main() !void { 49 | 50 | // glfw: initialize and configure 51 | // ------------------------------ 52 | if (!glfw.init(.{})) { 53 | std.log.err("GLFW initialization failed", .{}); 54 | return; 55 | } 56 | defer glfw.terminate(); 57 | 58 | // glfw window creation 59 | // -------------------- 60 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 61 | .opengl_profile = .opengl_core_profile, 62 | .context_version_major = 4, 63 | .context_version_minor = 1, 64 | }) orelse { 65 | std.log.err("GLFW Window creation failed", .{}); 66 | return; 67 | }; 68 | defer window.destroy(); 69 | 70 | glfw.makeContextCurrent(window); 71 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 72 | 73 | // Load all OpenGL function pointers 74 | // --------------------------------------- 75 | const proc: glfw.GLProc = undefined; 76 | try gl.load(proc, glGetProcAddress); 77 | 78 | // Create vertex shader 79 | var vertexShader: c_uint = undefined; 80 | vertexShader = gl.createShader(gl.VERTEX_SHADER); 81 | defer gl.deleteShader(vertexShader); 82 | 83 | // Attach the shader source to the vertex shader object and compile it 84 | gl.shaderSource(vertexShader, 1, @ptrCast([*c]const [*c]const u8, &vertexShaderSource), 0); 85 | gl.compileShader(vertexShader); 86 | 87 | // Check if vertex shader was compiled successfully 88 | var success: c_int = undefined; 89 | var infoLog: [512]u8 = [_]u8{0} ** 512; 90 | 91 | gl.getShaderiv(vertexShader, gl.COMPILE_STATUS, &success); 92 | 93 | if (success == 0) { 94 | gl.getShaderInfoLog(vertexShader, 512, 0, &infoLog); 95 | std.log.err("{s}", .{infoLog}); 96 | } 97 | 98 | // Fragment shader 99 | var fragmentShader_1: c_uint = undefined; 100 | fragmentShader_1 = gl.createShader(gl.FRAGMENT_SHADER); 101 | defer gl.deleteShader(fragmentShader_1); 102 | 103 | gl.shaderSource(fragmentShader_1, 1, @ptrCast([*c]const [*c]const u8, &fragmentShaderSource_1), 0); 104 | gl.compileShader(fragmentShader_1); 105 | 106 | gl.getShaderiv(fragmentShader_1, gl.COMPILE_STATUS, &success); 107 | 108 | if (success == 0) { 109 | gl.getShaderInfoLog(fragmentShader_1, 512, 0, &infoLog); 110 | std.log.err("{s}", .{infoLog}); 111 | } 112 | 113 | // Fragment shader 114 | var fragmentShader_2: c_uint = undefined; 115 | fragmentShader_2 = gl.createShader(gl.FRAGMENT_SHADER); 116 | defer gl.deleteShader(fragmentShader_2); 117 | 118 | gl.shaderSource(fragmentShader_2, 1, @ptrCast([*c]const [*c]const u8, &fragmentShaderSource_2), 0); 119 | gl.compileShader(fragmentShader_2); 120 | 121 | gl.getShaderiv(fragmentShader_2, gl.COMPILE_STATUS, &success); 122 | 123 | if (success == 0) { 124 | gl.getShaderInfoLog(fragmentShader_2, 512, 0, &infoLog); 125 | std.log.err("{s}", .{infoLog}); 126 | } 127 | 128 | // create a program object 129 | var shaderProgram_1: c_uint = undefined; 130 | shaderProgram_1 = gl.createProgram(); 131 | defer gl.deleteProgram(shaderProgram_1); 132 | 133 | // create a program object 134 | var shaderProgram_2: c_uint = undefined; 135 | shaderProgram_2 = gl.createProgram(); 136 | defer gl.deleteProgram(shaderProgram_2); 137 | 138 | // attach compiled shader objects to the program object and link 139 | gl.attachShader(shaderProgram_1, vertexShader); 140 | gl.attachShader(shaderProgram_1, fragmentShader_1); 141 | gl.linkProgram(shaderProgram_1); 142 | 143 | // attach compiled shader objects to the program object and link 144 | gl.attachShader(shaderProgram_2, vertexShader); 145 | gl.attachShader(shaderProgram_2, fragmentShader_2); 146 | gl.linkProgram(shaderProgram_2); 147 | 148 | // check if shader linking was successfull 149 | gl.getProgramiv(shaderProgram_1, gl.LINK_STATUS, &success); 150 | if (success == 0) { 151 | gl.getProgramInfoLog(shaderProgram_1, 512, 0, &infoLog); 152 | std.log.err("{s}", .{infoLog}); 153 | } 154 | 155 | // check if shader linking was successfull 156 | gl.getProgramiv(shaderProgram_2, gl.LINK_STATUS, &success); 157 | if (success == 0) { 158 | gl.getProgramInfoLog(shaderProgram_2, 512, 0, &infoLog); 159 | std.log.err("{s}", .{infoLog}); 160 | } 161 | 162 | // set up vertex data (and buffer(s)) and configure vertex attributes 163 | // ------------------------------------------------------------------ 164 | const vertices_1 = [9]f32 { 165 | // Triangle 1 166 | -1.0, -0.5, 0.0, 0.0, -0.5, 0.0, -0.5, 0.5, 0.0, 167 | }; 168 | 169 | const vertices_2 = [9]f32 { 170 | // Triangle 2 171 | 0.0, -0.5, 0.0, 1.0, -0.5, 0.0, 0.5, 0.5, 0.0 172 | }; 173 | 174 | var VBOs: [2]c_uint = undefined; 175 | var VAOs: [2]c_uint = undefined; 176 | 177 | gl.genVertexArrays(2, &VAOs); 178 | defer gl.deleteVertexArrays(2, &VAOs); 179 | 180 | gl.genBuffers(2, &VBOs); 181 | defer gl.deleteBuffers(1, &VBOs); 182 | 183 | // bind the Vertex Array Object for trangle 1 first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 184 | gl.bindVertexArray(VAOs[0]); 185 | gl.bindBuffer(gl.ARRAY_BUFFER, VBOs[0]); 186 | // Fill our buffer with the vertex data for traingle 1 187 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices_1.len, &vertices_1, gl.STATIC_DRAW); 188 | // Specify and link our vertext attribute description 189 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 3 * @sizeOf(f32), null); 190 | gl.enableVertexAttribArray(0); 191 | 192 | // bind the Vertex Array Object for triangle 2 first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 193 | gl.bindVertexArray(VAOs[1]); 194 | gl.bindBuffer(gl.ARRAY_BUFFER, VBOs[1]); 195 | // Fill our buffer with the vertex data 196 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices_2.len, &vertices_2, gl.STATIC_DRAW); 197 | // Specify and link our vertext attribute description 198 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 3 * @sizeOf(f32), null); 199 | gl.enableVertexAttribArray(0); 200 | 201 | // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind 202 | gl.bindBuffer(gl.ARRAY_BUFFER, 0); 203 | 204 | // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other 205 | // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. 206 | gl.bindVertexArray(0); 207 | 208 | while (!window.shouldClose()) { 209 | processInput(window); 210 | 211 | gl.clearColor(0.2, 0.3, 0.3, 1.0); 212 | gl.clear(gl.COLOR_BUFFER_BIT); 213 | 214 | // Activate shaderProgram 215 | gl.useProgram(shaderProgram_1); 216 | // Draw triangle 1 217 | gl.bindVertexArray(VAOs[0]); 218 | gl.drawArrays(gl.TRIANGLES, 0, 3); 219 | 220 | // Activate shaderProgram 221 | gl.useProgram(shaderProgram_2); 222 | // Draw triangle 2 223 | gl.bindVertexArray(VAOs[1]); 224 | gl.drawArrays(gl.TRIANGLES, 0, 3); 225 | 226 | window.swapBuffers(); 227 | glfw.pollEvents(); 228 | } 229 | } 230 | 231 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 232 | _ = p; 233 | return glfw.getProcAddress(proc); 234 | } 235 | 236 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 237 | _ = window; 238 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 239 | } 240 | 241 | fn processInput(window: glfw.Window) void { 242 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 243 | _ = glfw.Window.setShouldClose(window, true); 244 | } 245 | } 246 | 247 | ``` -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 10 | const exe = b.addExecutable(.{ 11 | .name = "hello_trangle", 12 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 13 | .target = options.target, 14 | .optimize = options.build_mode, 15 | }); 16 | 17 | return exe; 18 | } 19 | -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/excercise_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/hello_triangle/excercise_1.png -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/exercise_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/hello_triangle/exercise_2.png -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/exercise_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/hello_triangle/exercise_3.png -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/hello_triangle/image.png -------------------------------------------------------------------------------- /src/getting_started/hello_triangle/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const glfw = @import("glfw"); 3 | const gl = @import("gl"); 4 | 5 | const vertexShaderSource = 6 | \\ #version 410 core 7 | \\ layout (location = 0) in vec3 aPos; 8 | \\ void main() 9 | \\ { 10 | \\ gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 11 | \\ } 12 | ; 13 | 14 | const fragmentShaderSource = 15 | \\ #version 410 core 16 | \\ out vec4 FragColor; 17 | \\ void main() { 18 | \\ FragColor = vec4(1.0, 1.0, 0.2, 1.0); 19 | \\ } 20 | ; 21 | 22 | const WindowSize = struct { 23 | pub const width: u32 = 800; 24 | pub const height: u32 = 600; 25 | }; 26 | 27 | pub fn main() !void { 28 | 29 | // glfw: initialize and configure 30 | // ------------------------------ 31 | if (!glfw.init(.{})) { 32 | std.log.err("GLFW initialization failed", .{}); 33 | return; 34 | } 35 | defer glfw.terminate(); 36 | 37 | // glfw window creation 38 | // -------------------- 39 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 40 | .opengl_profile = .opengl_core_profile, 41 | .context_version_major = 4, 42 | .context_version_minor = 1, 43 | }) orelse { 44 | std.log.err("GLFW Window creation failed", .{}); 45 | return; 46 | }; 47 | defer window.destroy(); 48 | 49 | glfw.makeContextCurrent(window); 50 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 51 | 52 | // Load all OpenGL function pointers 53 | // --------------------------------------- 54 | const proc: glfw.GLProc = undefined; 55 | try gl.load(proc, glGetProcAddress); 56 | 57 | // Create vertex shader 58 | var vertexShader: c_uint = undefined; 59 | vertexShader = gl.createShader(gl.VERTEX_SHADER); 60 | defer gl.deleteShader(vertexShader); 61 | 62 | // Attach the shader source to the vertex shader object and compile it 63 | gl.shaderSource(vertexShader, 1, @ptrCast([*c]const [*c]const u8, &vertexShaderSource), 0); 64 | gl.compileShader(vertexShader); 65 | 66 | // Check if vertex shader was compiled successfully 67 | var success: c_int = undefined; 68 | var infoLog: [512]u8 = [_]u8{0} ** 512; 69 | 70 | gl.getShaderiv(vertexShader, gl.COMPILE_STATUS, &success); 71 | 72 | if (success == 0) { 73 | gl.getShaderInfoLog(vertexShader, 512, 0, &infoLog); 74 | std.log.err("{s}", .{infoLog}); 75 | } 76 | 77 | // Fragment shader 78 | var fragmentShader: c_uint = undefined; 79 | fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 80 | defer gl.deleteShader(fragmentShader); 81 | 82 | gl.shaderSource(fragmentShader, 1, @ptrCast([*c]const [*c]const u8, &fragmentShaderSource), 0); 83 | gl.compileShader(fragmentShader); 84 | 85 | gl.getShaderiv(fragmentShader, gl.COMPILE_STATUS, &success); 86 | 87 | if (success == 0) { 88 | gl.getShaderInfoLog(fragmentShader, 512, 0, &infoLog); 89 | std.log.err("{s}", .{infoLog}); 90 | } 91 | 92 | // create a program object 93 | var shaderProgram: c_uint = undefined; 94 | shaderProgram = gl.createProgram(); 95 | std.debug.print("{any}", .{shaderProgram}); 96 | defer gl.deleteProgram(shaderProgram); 97 | 98 | // attach compiled shader objects to the program object and link 99 | gl.attachShader(shaderProgram, vertexShader); 100 | gl.attachShader(shaderProgram, fragmentShader); 101 | gl.linkProgram(shaderProgram); 102 | 103 | // check if shader linking was successfull 104 | gl.getProgramiv(shaderProgram, gl.LINK_STATUS, &success); 105 | if (success == 0) { 106 | gl.getProgramInfoLog(shaderProgram, 512, 0, &infoLog); 107 | std.log.err("{s}", .{infoLog}); 108 | } 109 | 110 | // set up vertex data (and buffer(s)) and configure vertex attributes 111 | // ------------------------------------------------------------------ 112 | const vertices = [9]f32{ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0 }; 113 | var VBO: c_uint = undefined; 114 | var VAO: c_uint = undefined; 115 | 116 | gl.genVertexArrays(1, &VAO); 117 | defer gl.deleteVertexArrays(1, &VAO); 118 | 119 | gl.genBuffers(1, &VBO); 120 | defer gl.deleteBuffers(1, &VBO); 121 | 122 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 123 | gl.bindVertexArray(VAO); 124 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 125 | // Fill our buffer with the vertex data 126 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, gl.STATIC_DRAW); 127 | 128 | // Specify and link our vertext attribute description 129 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 3 * @sizeOf(f32), null); 130 | gl.enableVertexAttribArray(0); 131 | 132 | // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind 133 | 134 | // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other 135 | // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. 136 | 137 | while (!window.shouldClose()) { 138 | processInput(window); 139 | 140 | gl.clearColor(0.2, 0.3, 0.3, 1.0); 141 | gl.clear(gl.COLOR_BUFFER_BIT); 142 | 143 | // Activate shaderProgram 144 | gl.useProgram(shaderProgram); 145 | gl.bindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized 146 | gl.drawArrays(gl.TRIANGLES, 0, 3); 147 | 148 | window.swapBuffers(); 149 | glfw.pollEvents(); 150 | } 151 | } 152 | 153 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 154 | _ = p; 155 | return glfw.getProcAddress(proc); 156 | } 157 | 158 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 159 | _ = window; 160 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 161 | } 162 | 163 | fn processInput(window: glfw.Window) void { 164 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 165 | _ = glfw.Window.setShouldClose(window, true); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/getting_started/shaders/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "shaders/"; 10 | 11 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "shaders", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/shaders/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/shaders/image.png -------------------------------------------------------------------------------- /src/getting_started/shaders/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const glfw = @import("glfw"); 3 | const gl = @import("gl"); 4 | const Shader = @import("Shader"); 5 | 6 | const WindowSize = struct { 7 | pub const width: u32 = 800; 8 | pub const height: u32 = 600; 9 | }; 10 | 11 | pub fn main() !void { 12 | 13 | // glfw: initialize and configure 14 | // ------------------------------ 15 | if (!glfw.init(.{})) { 16 | std.log.err("GLFW initialization failed", .{}); 17 | return; 18 | } 19 | defer glfw.terminate(); 20 | 21 | // glfw window creation 22 | // -------------------- 23 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 24 | .opengl_profile = .opengl_core_profile, 25 | .context_version_major = 4, 26 | .context_version_minor = 1, 27 | }) orelse { 28 | std.log.err("GLFW Window creation failed", .{}); 29 | return; 30 | }; 31 | defer window.destroy(); 32 | 33 | glfw.makeContextCurrent(window); 34 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 35 | 36 | // Load all OpenGL function pointers 37 | // --------------------------------------- 38 | const proc: glfw.GLProc = undefined; 39 | try gl.load(proc, glGetProcAddress); 40 | 41 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 42 | var allocator = gpa.allocator(); 43 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 44 | defer arena_allocator_state.deinit(); 45 | const arena_allocator = arena_allocator_state.allocator(); 46 | 47 | // create shader program 48 | var shader_program: Shader = Shader.create(arena_allocator, "shaders/shader_ex3.vs", "shaders/shader_ex3.fs"); 49 | 50 | // set up vertex data (and buffer(s)) and configure vertex attributes 51 | // ------------------------------------------------------------------ 52 | const vertices = [_]f32{ 53 | -0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 54 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 55 | 0.0, 0.5, 0.0, 0.0, 0.0, 1.0, 56 | }; 57 | 58 | var VBO: c_uint = undefined; 59 | var VAO: c_uint = undefined; 60 | 61 | gl.genVertexArrays(1, &VAO); 62 | defer gl.deleteVertexArrays(1, &VAO); 63 | 64 | gl.genBuffers(1, &VBO); 65 | defer gl.deleteBuffers(1, &VBO); 66 | 67 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 68 | gl.bindVertexArray(VAO); 69 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 70 | // Fill our buffer with the vertex data 71 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, gl.STATIC_DRAW); 72 | 73 | // Specify and link our vertext attribute description 74 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 6 * @sizeOf(f32), null); 75 | gl.enableVertexAttribArray(0); 76 | 77 | const offset: [*c]c_uint = (3 * @sizeOf(f32)); 78 | gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 6 * @sizeOf(f32), offset); 79 | gl.enableVertexAttribArray(1); 80 | 81 | while (!window.shouldClose()) { 82 | processInput(window); 83 | 84 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 85 | gl.clear(gl.COLOR_BUFFER_BIT); 86 | 87 | // update the uniform color 88 | const timeValue = glfw.getTime(); 89 | const offsetValue = @floatCast(f32, @sin(timeValue * 2) / 2.0 + 0.5); 90 | 91 | shader_program.setVec3f("offset", [3]f32{ offsetValue, offsetValue, offsetValue }); 92 | shader_program.use(); 93 | 94 | gl.bindVertexArray(VAO); 95 | gl.drawArrays(gl.TRIANGLES, 0, 3); 96 | 97 | window.swapBuffers(); 98 | glfw.pollEvents(); 99 | } 100 | } 101 | 102 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 103 | _ = p; 104 | return glfw.getProcAddress(proc); 105 | } 106 | 107 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 108 | _ = window; 109 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 110 | } 111 | 112 | fn processInput(window: glfw.Window) void { 113 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 114 | _ = glfw.Window.setShouldClose(window, true); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/getting_started/shaders/shaders/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | in vec3 ourColor; 4 | void main() 5 | { 6 | FragColor = vec4(ourColor, 0.5); 7 | }; 8 | -------------------------------------------------------------------------------- /src/getting_started/shaders/shaders/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | 5 | uniform vec3 offset; 6 | 7 | out vec3 ourColor; 8 | 9 | void main() 10 | { 11 | gl_Position = vec4(aPos + offset, 1.0f); 12 | ourColor = aColor; 13 | } -------------------------------------------------------------------------------- /src/getting_started/shaders/shaders/shader_ex3.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | in vec3 ourColor; 4 | in vec3 vertPos; 5 | void main() 6 | { 7 | FragColor = vec4(vertPos, 1.0); 8 | }; 9 | -------------------------------------------------------------------------------- /src/getting_started/shaders/shaders/shader_ex3.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | 5 | uniform vec3 offset; 6 | 7 | out vec3 ourColor; 8 | out vec3 vertPos; 9 | 10 | void main() 11 | { 12 | gl_Position = vec4(aPos * offset, 1.0f); 13 | ourColor = aColor; 14 | vertPos = aPos + offset; 15 | } -------------------------------------------------------------------------------- /src/getting_started/simple_camera/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "content/"; 10 | 11 | pub fn build(b: *std.build.Builder, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "simple_camera", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/simple_camera/content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/simple_camera/content/awesomeface.png -------------------------------------------------------------------------------- /src/getting_started/simple_camera/content/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/simple_camera/content/container.jpg -------------------------------------------------------------------------------- /src/getting_started/simple_camera/content/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 FragColor; 4 | in vec2 texCoord; 5 | 6 | uniform sampler2D texture1; 7 | uniform sampler2D texture2; 8 | 9 | void main() 10 | { 11 | FragColor = mix(texture(texture1, texCoord),texture(texture2, texCoord), 0.5f); 12 | }; 13 | -------------------------------------------------------------------------------- /src/getting_started/simple_camera/content/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec2 aTexCoord; 4 | 5 | out vec2 texCoord; 6 | 7 | uniform mat4 model; 8 | uniform mat4 view; 9 | uniform mat4 projection; 10 | 11 | void main() 12 | { 13 | gl_Position = projection * view * model * vec4(aPos, 1.0f); 14 | texCoord = aTexCoord; 15 | } -------------------------------------------------------------------------------- /src/getting_started/simple_camera/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | const glfw = @import("glfw"); 4 | const zstbi = @import("zstbi"); 5 | const zm = @import("zmath"); 6 | const gl = @import("gl"); 7 | const Shader = @import("Shader"); 8 | const Camera = @import("Camera"); 9 | const common = @import("common"); 10 | 11 | // Camera 12 | const camera_pos = zm.loadArr3(.{ 0.0, 0.0, 5.0 }); 13 | var lastX: f64 = 0.0; 14 | var lastY: f64 = 0.0; 15 | var first_mouse = true; 16 | var camera = Camera.camera(camera_pos); 17 | 18 | // Timing 19 | var delta_time: f32 = 0.0; 20 | var last_frame: f32 = 0.0; 21 | 22 | const WindowSize = struct { 23 | pub const width: u32 = 800; 24 | pub const height: u32 = 600; 25 | }; 26 | 27 | pub fn main() !void { 28 | 29 | // glfw: initialize and configure 30 | // ------------------------------ 31 | if (!glfw.init(.{})) { 32 | std.log.err("GLFW initialization failed", .{}); 33 | return; 34 | } 35 | defer glfw.terminate(); 36 | 37 | // glfw window creation 38 | // -------------------- 39 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 40 | .opengl_profile = .opengl_core_profile, 41 | .context_version_major = 4, 42 | .context_version_minor = 1, 43 | }) orelse { 44 | std.log.err("GLFW Window creation failed", .{}); 45 | return; 46 | }; 47 | defer window.destroy(); 48 | 49 | glfw.makeContextCurrent(window); 50 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 51 | // Capture mouse, disable cursor visibility 52 | glfw.Window.setInputMode(window, glfw.Window.InputMode.cursor, glfw.Window.InputModeCursor.disabled); 53 | glfw.Window.setCursorPosCallback(window, mouseCallback); 54 | glfw.Window.setScrollCallback(window, mouseScrollCallback); 55 | 56 | // Load all OpenGL function pointers 57 | // --------------------------------------- 58 | const proc: glfw.GLProc = undefined; 59 | try gl.load(proc, glGetProcAddress); 60 | 61 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 62 | var allocator = gpa.allocator(); 63 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 64 | defer arena_allocator_state.deinit(); 65 | const arena = arena_allocator_state.allocator(); 66 | 67 | // create shader program 68 | var shader_program: Shader = Shader.create(arena, "content/shader.vs", "content/shader.fs"); 69 | 70 | // set up vertex data (and buffer(s)) and configure vertex attributes 71 | // ------------------------------------------------------------------ 72 | 73 | const vertices_2D = [_]f32{ 74 | // positions // colors // texture coords 75 | 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right 76 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right 77 | -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left 78 | -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top left 79 | }; 80 | 81 | _ = vertices_2D; 82 | 83 | const vertices_3D = [_]f32{ -0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, -0.5, -0.5, -0.5, 0.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, 1.0, -0.5, 0.5, 0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, -0.5, 1.0, 1.0, -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, -0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, 1.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 1.0 }; 84 | 85 | const cube_positions = [_][3]f32{ .{ 0.0, 0.0, 0.0 }, .{ 2.0, 5.0, -15.0 }, .{ -1.5, -2.2, -2.5 }, .{ -3.8, -2.0, -12.3 }, .{ 2.4, -0.4, -3.5 }, .{ -1.7, 3.0, -7.5 }, .{ 1.3, -2.0, -2.5 }, .{ 1.5, 2.0, -2.5 }, .{ 1.5, 0.2, -1.5 }, .{ -1.3, 1.0, -1.5 } }; 86 | 87 | var VBO: c_uint = undefined; 88 | var VAO: c_uint = undefined; 89 | 90 | gl.genVertexArrays(1, &VAO); 91 | defer gl.deleteVertexArrays(1, &VAO); 92 | 93 | gl.genBuffers(1, &VBO); 94 | defer gl.deleteBuffers(1, &VBO); 95 | 96 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 97 | gl.bindVertexArray(VAO); 98 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 99 | // Fill our buffer with the vertex data 100 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices_3D.len, &vertices_3D, gl.STATIC_DRAW); 101 | 102 | // vertex 103 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 5 * @sizeOf(f32), null); 104 | gl.enableVertexAttribArray(0); 105 | 106 | // texture coords 107 | const tex_offset: [*c]c_uint = (3 * @sizeOf(f32)); 108 | gl.vertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 5 * @sizeOf(f32), tex_offset); 109 | gl.enableVertexAttribArray(1); 110 | 111 | // zstbi: loading an image. 112 | zstbi.init(allocator); 113 | defer zstbi.deinit(); 114 | 115 | const image1_path = common.pathToContent(arena, "content/container.jpg") catch unreachable; 116 | var image1 = try zstbi.Image.init(&image1_path, 0); 117 | defer image1.deinit(); 118 | std.debug.print("\nImage 1 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", .{ image1.width, image1.height, image1.num_components }); 119 | 120 | zstbi.setFlipVerticallyOnLoad(true); 121 | const image2_path = common.pathToContent(arena, "content/awesomeface.png") catch unreachable; 122 | var image2 = try zstbi.Image.init(&image2_path, 0); 123 | defer image2.deinit(); 124 | std.debug.print("\nImage 2 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", .{ image2.width, image2.height, image2.num_components }); 125 | 126 | // Create and bind texture1 resource 127 | var texture1: c_uint = undefined; 128 | 129 | gl.genTextures(1, &texture1); 130 | gl.activeTexture(gl.TEXTURE0); // activate the texture unit first before binding texture 131 | gl.bindTexture(gl.TEXTURE_2D, texture1); 132 | 133 | // set the texture1 wrapping parameters 134 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 135 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 136 | // set texture1 filtering parameters 137 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 138 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 139 | 140 | // Generate the texture1 141 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image1.width), @intCast(c_int, image1.height), 0, gl.RGB, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image1.data)); 142 | gl.generateMipmap(gl.TEXTURE_2D); 143 | 144 | // Texture2 145 | var texture2: c_uint = undefined; 146 | 147 | gl.genTextures(1, &texture2); 148 | gl.activeTexture(gl.TEXTURE1); // activate the texture unit first before binding texture 149 | gl.bindTexture(gl.TEXTURE_2D, texture2); 150 | 151 | // set the texture1 wrapping parameters 152 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 153 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 154 | // set texture1 filtering parameters 155 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 156 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 157 | 158 | // Generate the texture1 159 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image2.width), @intCast(c_int, image2.height), 0, gl.RGBA, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image2.data)); 160 | gl.generateMipmap(gl.TEXTURE_2D); 161 | 162 | // Enable OpenGL depth testing (use Z-buffer information) 163 | gl.enable(gl.DEPTH_TEST); 164 | 165 | shader_program.use(); 166 | shader_program.setInt("texture1", 0); 167 | shader_program.setInt("texture2", 1); 168 | 169 | // Buffer to store Model matrix 170 | var model: [16]f32 = undefined; 171 | 172 | // View matrix 173 | var view: [16]f32 = undefined; 174 | 175 | // Buffer to store Orojection matrix (in render loop) 176 | var proj: [16]f32 = undefined; 177 | 178 | while (!window.shouldClose()) { 179 | 180 | // Time per frame 181 | const current_frame = @floatCast(f32, glfw.getTime()); 182 | delta_time = current_frame - last_frame; 183 | last_frame = current_frame; 184 | 185 | processInput(window); 186 | 187 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 188 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 189 | gl.activeTexture(gl.TEXTURE0); 190 | gl.bindTexture(gl.TEXTURE_2D, texture1); 191 | gl.activeTexture(gl.TEXTURE1); 192 | gl.bindTexture(gl.TEXTURE_2D, texture2); 193 | gl.bindVertexArray(VAO); 194 | 195 | // Projection matrix 196 | const projM = x: { 197 | const window_size = window.getSize(); 198 | const aspect = @intToFloat(f32, window_size.width) / @intToFloat(f32, window_size.height); 199 | var projM = zm.perspectiveFovRhGl(camera.zoom * common.RAD_CONVERSION, aspect, 0.1, 100.0); 200 | break :x projM; 201 | }; 202 | zm.storeMat(&proj, projM); 203 | shader_program.setMat4f("projection", proj); 204 | 205 | // View matrix: Camera 206 | const viewM = camera.getViewMatrix(); 207 | zm.storeMat(&view, viewM); 208 | shader_program.setMat4f("view", view); 209 | 210 | for (cube_positions, 0..) |cube_position, i| { 211 | // Model matrix 212 | const cube_trans = zm.translation(cube_position[0], cube_position[1], cube_position[2]); 213 | const rotation_direction = (((@mod(@intToFloat(f32, i + 1), 2.0)) * 2.0) - 1.0); 214 | const cube_rot = zm.matFromAxisAngle(zm.f32x4(1.0, 0.3, 0.5, 1.0), @floatCast(f32, glfw.getTime()) * 55.0 * rotation_direction * common.RAD_CONVERSION); 215 | const modelM = zm.mul(cube_rot, cube_trans); 216 | zm.storeMat(&model, modelM); 217 | shader_program.setMat4f("model", model); 218 | 219 | gl.drawArrays(gl.TRIANGLES, 0, 36); 220 | } 221 | 222 | window.swapBuffers(); 223 | glfw.pollEvents(); 224 | } 225 | } 226 | 227 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 228 | _ = p; 229 | return glfw.getProcAddress(proc); 230 | } 231 | 232 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 233 | _ = window; 234 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 235 | } 236 | 237 | fn processInput(window: glfw.Window) void { 238 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 239 | _ = glfw.Window.setShouldClose(window, true); 240 | } 241 | 242 | if (glfw.Window.getKey(window, glfw.Key.w) == glfw.Action.press) { 243 | camera.processKeyboard(Camera.CameraMovement.FORWARD, delta_time); 244 | } 245 | if (glfw.Window.getKey(window, glfw.Key.s) == glfw.Action.press) { 246 | camera.processKeyboard(Camera.CameraMovement.BACKWARD, delta_time); 247 | } 248 | if (glfw.Window.getKey(window, glfw.Key.a) == glfw.Action.press) { 249 | camera.processKeyboard(Camera.CameraMovement.LEFT, delta_time); 250 | } 251 | if (glfw.Window.getKey(window, glfw.Key.d) == glfw.Action.press) { 252 | camera.processKeyboard(Camera.CameraMovement.RIGHT, delta_time); 253 | } 254 | } 255 | 256 | fn mouseCallback(window: glfw.Window, xpos: f64, ypos: f64) void { 257 | _ = window; 258 | 259 | if (first_mouse) { 260 | lastX = xpos; 261 | lastY = ypos; 262 | first_mouse = false; 263 | } 264 | 265 | var xoffset = xpos - lastX; 266 | var yoffset = ypos - lastY; 267 | 268 | lastX = xpos; 269 | lastY = ypos; 270 | 271 | camera.processMouseMovement(xoffset, yoffset, true); 272 | } 273 | 274 | fn mouseScrollCallback(window: glfw.Window, xoffset: f64, yoffset: f64) void { 275 | _ = window; 276 | _ = xoffset; 277 | 278 | camera.processMouseScroll(yoffset); 279 | } 280 | -------------------------------------------------------------------------------- /src/getting_started/textures/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "content/"; 10 | 11 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "textures", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/textures/content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/textures/content/awesomeface.png -------------------------------------------------------------------------------- /src/getting_started/textures/content/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/textures/content/container.jpg -------------------------------------------------------------------------------- /src/getting_started/textures/content/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 FragColor; 4 | in vec3 ourColor; 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture1; 8 | uniform sampler2D texture2; 9 | 10 | void main() 11 | { 12 | FragColor = mix(texture(texture1, texCoord),texture(texture2, texCoord), 0.5f) * vec4(ourColor, 1.0f); 13 | }; 14 | -------------------------------------------------------------------------------- /src/getting_started/textures/content/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | layout (location = 2) in vec2 aTexCoord; 5 | 6 | out vec3 ourColor; 7 | out vec2 texCoord; 8 | 9 | void main() 10 | { 11 | gl_Position = vec4(aPos, 1.0f); 12 | ourColor = aColor; 13 | texCoord = aTexCoord; 14 | } -------------------------------------------------------------------------------- /src/getting_started/textures/content/wall.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/textures/content/wall.jpg -------------------------------------------------------------------------------- /src/getting_started/textures/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/textures/image.png -------------------------------------------------------------------------------- /src/getting_started/textures/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const glfw = @import("glfw"); 3 | const zstbi = @import("zstbi"); 4 | const gl = @import("gl"); 5 | const Shader = @import("Shader"); 6 | const common = @import("common"); 7 | 8 | const WindowSize = struct { 9 | pub const width: u32 = 800; 10 | pub const height: u32 = 600; 11 | }; 12 | 13 | pub fn main() !void { 14 | 15 | // glfw: initialize and configure 16 | // ------------------------------ 17 | if (!glfw.init(.{})) { 18 | std.log.err("GLFW initialization failed", .{}); 19 | return; 20 | } 21 | defer glfw.terminate(); 22 | 23 | // glfw window creation 24 | // -------------------- 25 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 26 | .opengl_profile = .opengl_core_profile, 27 | .context_version_major = 4, 28 | .context_version_minor = 1, 29 | }) orelse { 30 | std.log.err("GLFW Window creation failed", .{}); 31 | return; 32 | }; 33 | defer window.destroy(); 34 | 35 | glfw.makeContextCurrent(window); 36 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 37 | 38 | // Load all OpenGL function pointers 39 | // --------------------------------------- 40 | const proc: glfw.GLProc = undefined; 41 | try gl.load(proc, glGetProcAddress); 42 | 43 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 44 | var allocator = gpa.allocator(); 45 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 46 | defer arena_allocator_state.deinit(); 47 | const arena = arena_allocator_state.allocator(); 48 | 49 | // create shader program 50 | var shader_program: Shader = Shader.create( 51 | arena, 52 | "content/shader.vs", 53 | "content/shader.fs", 54 | ); 55 | 56 | // set up vertex data (and buffer(s)) and configure vertex attributes 57 | // ------------------------------------------------------------------ 58 | 59 | const vertices = [_]f32{ 60 | // positions // colors // texture coords 61 | 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right 62 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right 63 | -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left 64 | -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top left 65 | }; 66 | 67 | const indices = [_]c_uint{ 68 | // note that we start from 0! 69 | 0, 1, 3, // first triangle 70 | 1, 2, 3, // second triangle 71 | }; 72 | 73 | var VBO: c_uint = undefined; 74 | var VAO: c_uint = undefined; 75 | var EBO: c_uint = undefined; 76 | 77 | gl.genVertexArrays(1, &VAO); 78 | defer gl.deleteVertexArrays(1, &VAO); 79 | 80 | gl.genBuffers(1, &VBO); 81 | defer gl.deleteBuffers(1, &VBO); 82 | 83 | gl.genBuffers(1, &EBO); 84 | defer gl.deleteBuffers(1, &EBO); 85 | 86 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 87 | gl.bindVertexArray(VAO); 88 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 89 | // Fill our buffer with the vertex data 90 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, gl.STATIC_DRAW); 91 | 92 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO); 93 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 6 * @sizeOf(c_uint), &indices, gl.STATIC_DRAW); 94 | 95 | // vertex 96 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), null); 97 | gl.enableVertexAttribArray(0); 98 | 99 | // colors 100 | const col_offset: [*c]c_uint = (3 * @sizeOf(f32)); 101 | gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), col_offset); 102 | gl.enableVertexAttribArray(1); 103 | 104 | // texture coords 105 | const tex_offset: [*c]c_uint = (6 * @sizeOf(f32)); 106 | gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), tex_offset); 107 | gl.enableVertexAttribArray(2); 108 | 109 | // zstbi: loading an image. 110 | zstbi.init(allocator); 111 | defer zstbi.deinit(); 112 | 113 | const image1_path = common.pathToContent(arena, "content/container.jpg") catch unreachable; 114 | var image1 = try zstbi.Image.init(&image1_path, 0); 115 | defer image1.deinit(); 116 | std.debug.print( 117 | "\nImage 1 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", 118 | .{ image1.width, image1.height, image1.num_components }, 119 | ); 120 | 121 | zstbi.setFlipVerticallyOnLoad(true); 122 | const image2_path = common.pathToContent(arena, "content/awesomeface.png") catch unreachable; 123 | var image2 = try zstbi.Image.init(&image2_path, 0); 124 | defer image2.deinit(); 125 | std.debug.print( 126 | "\nImage 2 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", 127 | .{ image2.width, image2.height, image2.num_components }, 128 | ); 129 | 130 | // Create and bind texture1 resource 131 | var texture1: c_uint = undefined; 132 | 133 | gl.genTextures(1, &texture1); 134 | gl.activeTexture(gl.TEXTURE0); // activate the texture unit first before binding texture 135 | gl.bindTexture(gl.TEXTURE_2D, texture1); 136 | 137 | // set the texture1 wrapping parameters 138 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 139 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 140 | // set texture1 filtering parameters 141 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 142 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 143 | 144 | // Generate the texture1 145 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image1.width), @intCast(c_int, image1.height), 0, gl.RGB, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image1.data)); 146 | gl.generateMipmap(gl.TEXTURE_2D); 147 | 148 | // Texture2 149 | var texture2: c_uint = undefined; 150 | 151 | gl.genTextures(1, &texture2); 152 | gl.activeTexture(gl.TEXTURE1); // activate the texture unit first before binding texture 153 | gl.bindTexture(gl.TEXTURE_2D, texture2); 154 | 155 | // set the texture1 wrapping parameters 156 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 157 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 158 | // set texture1 filtering parameters 159 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 160 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 161 | 162 | // Generate the texture1 163 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image2.width), @intCast(c_int, image2.height), 0, gl.RGBA, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image2.data)); 164 | gl.generateMipmap(gl.TEXTURE_2D); 165 | 166 | shader_program.use(); 167 | shader_program.setInt("texture1", 0); 168 | shader_program.setInt("texture2", 1); 169 | 170 | while (!window.shouldClose()) { 171 | processInput(window); 172 | 173 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 174 | gl.clear(gl.COLOR_BUFFER_BIT); 175 | gl.activeTexture(gl.TEXTURE0); 176 | gl.bindTexture(gl.TEXTURE_2D, texture1); 177 | gl.activeTexture(gl.TEXTURE1); 178 | gl.bindTexture(gl.TEXTURE_2D, texture2); 179 | gl.bindVertexArray(VAO); 180 | gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); 181 | 182 | window.swapBuffers(); 183 | glfw.pollEvents(); 184 | } 185 | } 186 | 187 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 188 | _ = p; 189 | return glfw.getProcAddress(proc); 190 | } 191 | 192 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 193 | _ = window; 194 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 195 | } 196 | 197 | fn processInput(window: glfw.Window) void { 198 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 199 | _ = glfw.Window.setShouldClose(window, true); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/getting_started/transformations/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Options = @import("../../../build.zig").Options; 4 | 5 | inline fn thisDir() []const u8 { 6 | return comptime std.fs.path.dirname(@src().file) orelse "."; 7 | } 8 | 9 | const content_dir = "content/"; 10 | 11 | pub fn build(b: *std.Build, options: Options) *std.build.CompileStep { 12 | const exe = b.addExecutable(.{ 13 | .name = "transformations", 14 | .root_source_file = .{ .path = thisDir() ++ "/main.zig" }, 15 | .target = options.target, 16 | .optimize = options.build_mode, 17 | }); 18 | 19 | const install_content_step = b.addInstallDirectory(.{ 20 | .source_dir = thisDir() ++ "/" ++ content_dir, 21 | .install_dir = .{ .custom = "" }, 22 | .install_subdir = "bin/" ++ content_dir, 23 | }); 24 | exe.step.dependOn(&install_content_step.step); 25 | 26 | return exe; 27 | } 28 | -------------------------------------------------------------------------------- /src/getting_started/transformations/content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/transformations/content/awesomeface.png -------------------------------------------------------------------------------- /src/getting_started/transformations/content/container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/transformations/content/container.jpg -------------------------------------------------------------------------------- /src/getting_started/transformations/content/shader.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 FragColor; 4 | in vec3 ourColor; 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture1; 8 | uniform sampler2D texture2; 9 | 10 | void main() 11 | { 12 | FragColor = mix(texture(texture1, texCoord),texture(texture2, texCoord), 0.5f) * vec4(ourColor, 1.0f); 13 | }; 14 | -------------------------------------------------------------------------------- /src/getting_started/transformations/content/shader.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | layout (location = 2) in vec2 aTexCoord; 5 | 6 | out vec3 ourColor; 7 | out vec2 texCoord; 8 | 9 | uniform mat4 transform; 10 | 11 | void main() 12 | { 13 | gl_Position = transform * vec4(aPos, 1.0f); 14 | ourColor = aColor; 15 | texCoord = aTexCoord; 16 | } -------------------------------------------------------------------------------- /src/getting_started/transformations/content/wall.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/transformations/content/wall.jpg -------------------------------------------------------------------------------- /src/getting_started/transformations/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftlinks/zig_learn_opengl/7208f4402c44d9cb697705218b2bbbc82a8ccdd2/src/getting_started/transformations/image.png -------------------------------------------------------------------------------- /src/getting_started/transformations/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | const glfw = @import("glfw"); 4 | const zstbi = @import("zstbi"); 5 | const zm = @import("zmath"); 6 | const gl = @import("gl"); 7 | const Shader = @import("Shader"); 8 | const common = @import("common"); 9 | 10 | const WindowSize = struct { 11 | pub const width: u32 = 800; 12 | pub const height: u32 = 600; 13 | }; 14 | 15 | pub fn main() !void { 16 | 17 | // glfw: initialize and configure 18 | // ------------------------------ 19 | if (!glfw.init(.{})) { 20 | std.log.err("GLFW initialization failed", .{}); 21 | return; 22 | } 23 | defer glfw.terminate(); 24 | 25 | // glfw window creation 26 | // -------------------- 27 | const window = glfw.Window.create(WindowSize.width, WindowSize.height, "mach-glfw + zig-opengl", null, null, .{ 28 | .opengl_profile = .opengl_core_profile, 29 | .context_version_major = 4, 30 | .context_version_minor = 1, 31 | }) orelse { 32 | std.log.err("GLFW Window creation failed", .{}); 33 | return; 34 | }; 35 | defer window.destroy(); 36 | 37 | glfw.makeContextCurrent(window); 38 | glfw.Window.setFramebufferSizeCallback(window, framebuffer_size_callback); 39 | 40 | // Load all OpenGL function pointers 41 | // --------------------------------------- 42 | const proc: glfw.GLProc = undefined; 43 | try gl.load(proc, glGetProcAddress); 44 | 45 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 46 | var allocator = gpa.allocator(); 47 | var arena_allocator_state = std.heap.ArenaAllocator.init(allocator); 48 | defer arena_allocator_state.deinit(); 49 | const arena = arena_allocator_state.allocator(); 50 | 51 | // create shader program 52 | var shader_program: Shader = Shader.create(arena, "content/shader.vs", "content/shader.fs"); 53 | 54 | // set up vertex data (and buffer(s)) and configure vertex attributes 55 | // ------------------------------------------------------------------ 56 | 57 | const vertices = [_]f32{ 58 | // positions // colors // texture coords 59 | 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right 60 | 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right 61 | -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left 62 | -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top left 63 | }; 64 | 65 | const indices = [_]c_uint{ 66 | // note that we start from 0! 67 | 0, 1, 3, // first triangle 68 | 1, 2, 3, // second triangle 69 | }; 70 | 71 | var VBO: c_uint = undefined; 72 | var VAO: c_uint = undefined; 73 | var EBO: c_uint = undefined; 74 | 75 | gl.genVertexArrays(1, &VAO); 76 | defer gl.deleteVertexArrays(1, &VAO); 77 | 78 | gl.genBuffers(1, &VBO); 79 | defer gl.deleteBuffers(1, &VBO); 80 | 81 | gl.genBuffers(1, &EBO); 82 | defer gl.deleteBuffers(1, &EBO); 83 | 84 | // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 85 | gl.bindVertexArray(VAO); 86 | gl.bindBuffer(gl.ARRAY_BUFFER, VBO); 87 | // Fill our buffer with the vertex data 88 | gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, gl.STATIC_DRAW); 89 | 90 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO); 91 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 6 * @sizeOf(c_uint), &indices, gl.STATIC_DRAW); 92 | 93 | // vertex 94 | gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), null); 95 | gl.enableVertexAttribArray(0); 96 | 97 | // colors 98 | const col_offset: [*c]c_uint = (3 * @sizeOf(f32)); 99 | gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), col_offset); 100 | gl.enableVertexAttribArray(1); 101 | 102 | // texture coords 103 | const tex_offset: [*c]c_uint = (6 * @sizeOf(f32)); 104 | gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), tex_offset); 105 | gl.enableVertexAttribArray(2); 106 | 107 | // zstbi: loading an image. 108 | zstbi.init(allocator); 109 | defer zstbi.deinit(); 110 | 111 | const image1_path = common.pathToContent(arena, "content/container.jpg") catch unreachable; 112 | var image1 = try zstbi.Image.init(&image1_path, 0); 113 | defer image1.deinit(); 114 | std.debug.print("\nImage 1 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", .{ image1.width, image1.height, image1.num_components }); 115 | 116 | zstbi.setFlipVerticallyOnLoad(true); 117 | const image2_path = common.pathToContent(arena, "content/awesomeface.png") catch unreachable; 118 | var image2 = try zstbi.Image.init(&image2_path, 0); 119 | defer image2.deinit(); 120 | std.debug.print("\nImage 2 info:\n\n img width: {any}\n img height: {any}\n nchannels: {any}\n", .{ image2.width, image2.height, image2.num_components }); 121 | 122 | // Create and bind texture1 resource 123 | var texture1: c_uint = undefined; 124 | 125 | gl.genTextures(1, &texture1); 126 | gl.activeTexture(gl.TEXTURE0); // activate the texture unit first before binding texture 127 | gl.bindTexture(gl.TEXTURE_2D, texture1); 128 | 129 | // set the texture1 wrapping parameters 130 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 131 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 132 | // set texture1 filtering parameters 133 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 134 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 135 | 136 | // Generate the texture1 137 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image1.width), @intCast(c_int, image1.height), 0, gl.RGB, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image1.data)); 138 | gl.generateMipmap(gl.TEXTURE_2D); 139 | 140 | // Texture2 141 | var texture2: c_uint = undefined; 142 | 143 | gl.genTextures(1, &texture2); 144 | gl.activeTexture(gl.TEXTURE1); // activate the texture unit first before binding texture 145 | gl.bindTexture(gl.TEXTURE_2D, texture2); 146 | 147 | // set the texture1 wrapping parameters 148 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) 149 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 150 | // set texture1 filtering parameters 151 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 152 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 153 | 154 | // Generate the texture1 155 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, @intCast(c_int, image2.width), @intCast(c_int, image2.height), 0, gl.RGBA, gl.UNSIGNED_BYTE, @ptrCast([*c]const u8, image2.data)); 156 | gl.generateMipmap(gl.TEXTURE_2D); 157 | 158 | shader_program.use(); 159 | shader_program.setInt("texture1", 0); 160 | shader_program.setInt("texture2", 1); 161 | 162 | while (!window.shouldClose()) { 163 | processInput(window); 164 | 165 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 166 | gl.clear(gl.COLOR_BUFFER_BIT); 167 | gl.activeTexture(gl.TEXTURE0); 168 | gl.bindTexture(gl.TEXTURE_2D, texture1); 169 | gl.activeTexture(gl.TEXTURE1); 170 | gl.bindTexture(gl.TEXTURE_2D, texture2); 171 | gl.bindVertexArray(VAO); 172 | 173 | // Construction of the tranformation matrix 174 | const rotZ = zm.rotationZ(@floatCast(f32, glfw.getTime())); 175 | const scale = zm.scaling(0.5, 0.5, 0.5); 176 | const transformM = zm.mul(rotZ, scale); 177 | var transform: [16]f32 = undefined; 178 | zm.storeMat(&transform, transformM); 179 | 180 | // Sending our transformation matrix to our vertex shader 181 | const transformLoc = gl.getUniformLocation(shader_program.ID, "transform"); 182 | gl.uniformMatrix4fv(transformLoc, 1, gl.FALSE, &transform); 183 | 184 | gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); 185 | 186 | window.swapBuffers(); 187 | glfw.pollEvents(); 188 | } 189 | } 190 | 191 | fn glGetProcAddress(p: glfw.GLProc, proc: [:0]const u8) ?gl.FunctionPointer { 192 | _ = p; 193 | return glfw.getProcAddress(proc); 194 | } 195 | 196 | fn framebuffer_size_callback(window: glfw.Window, width: u32, height: u32) void { 197 | _ = window; 198 | gl.viewport(0, 0, @intCast(c_int, width), @intCast(c_int, height)); 199 | } 200 | 201 | fn processInput(window: glfw.Window) void { 202 | if (glfw.Window.getKey(window, glfw.Key.escape) == glfw.Action.press) { 203 | _ = glfw.Window.setShouldClose(window, true); 204 | } 205 | } 206 | --------------------------------------------------------------------------------