├── .gitignore ├── .vscode └── tasks.json ├── HELP.md ├── LICENSE ├── README.md ├── build.zig ├── examples └── events.zig ├── src ├── app.zig ├── app │ ├── event.zig │ ├── util.zig │ ├── windows.zig │ └── windows │ │ ├── backend │ │ ├── directx11.zig │ │ └── opengl.zig │ │ └── native.zig ├── audio.zig ├── audio │ ├── backend │ │ └── winnm.zig │ ├── buffer.zig │ └── mixer.zig ├── gfx.zig ├── gfx │ ├── backend.zig │ ├── backend │ │ ├── dx11.zig │ │ ├── metal.zig │ │ └── opengl.zig │ └── spritebatch.zig ├── lib.zig ├── lib │ └── slotmap.zig └── ziglet.zig └── ziglet.code-workspace /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | zig-cache/ 3 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "zig build", 8 | "type": "shell", 9 | "command": "zig build", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | ## directx 2 | - http://www.directxtutorial.com/Lesson.aspx?lessonid=11-1-3 3 | - https://github.com/floooh/sokol 4 | - https://github.com/bkaradzic/bgfx 5 | - https://bitbucket.org/rmitton/tigr 6 | 7 | ## app 8 | - https://github.com/martincohen/Punity 9 | - https://github.com/floooh/sokol 10 | - https://github.com/emoon/rust_minifb 11 | - https://github.com/floooh/sokol 12 | 13 | 14 | ## audio 15 | - https://github.com/dr-soft/mini_al 16 | - https://github.com/yui0/aplay- 17 | - https://github.com/jarikomppa/soloud 18 | - https://github.com/floooh/sokol 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2018 emekoi 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ziglet 2 | 3 | a small zig game library inspired by [sokol](https://github.com/floooh/sokol), [Punity](https://github.com/martincohen/Punity), [tigr](https://bitbucket.org/rmitton/tigr), and [mini_fb](https://github.com/emoon/rust_minifb). 4 | 5 | ## features 6 | * native: no dependencies at all 7 | 8 | ## TODO 9 | | | windows | macosx | linux | 10 | |----------------------|---------|---------|---------| 11 | | windowing | OK | planned | planned | 12 | | event handling | OK | planned | planned | 13 | | opengl 2.1 backend | planned | planned | planned | 14 | | directx11 backend | planned | N/A | N/A | 15 | | metal backend | N/A | planned | N/A | 16 | | vulkan backend | maybe? | maybe? | maybe? | 17 | | graphics abstrations | planned | planned | planned | 18 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const Builder = @import("std").build.Builder; 2 | 3 | pub const Example = struct { 4 | description: ?[]const u8, 5 | output: []const u8, 6 | input: []const u8, 7 | 8 | pub fn new(output: []const u8, input: []const u8, desc: ?[]const u8) Example { 9 | return Example{ 10 | .description = desc, 11 | .output = output, 12 | .input = input, 13 | }; 14 | } 15 | }; 16 | 17 | const examples = []const Example{Example.new("events", "examples/events.zig", null)}; 18 | 19 | pub fn build(builder: *Builder) void { 20 | const mode = builder.standardReleaseOptions(); 21 | builder.setInstallPrefix("."); 22 | 23 | // building and running examples 24 | const examples_step = builder.step("examples", "build the examples"); 25 | builder.default_step.dependOn(examples_step); 26 | 27 | for (examples) |example| { 28 | const exe = builder.addExecutable(example.output, example.input); 29 | exe.addPackagePath("ziglet", "src/ziglet.zig"); 30 | examples_step.dependOn(&exe.step); 31 | builder.installArtifact(exe); 32 | exe.setBuildMode(mode); 33 | 34 | const run_cmd = exe.run(); 35 | const run_step = builder.step(builder.fmt("run-{}", example.output), example.description orelse builder.fmt("run \"{}\" example", example.output)); 36 | run_step.dependOn(&run_cmd.step); 37 | } 38 | 39 | // formatting source files 40 | const fmt_step = builder.step("fmt", "format source files"); 41 | const fmt_run = builder.addSystemCommand([]const []const u8{ 42 | builder.zig_exe, "fmt", "examples", "src", "build.zig", 43 | }); 44 | fmt_step.dependOn(&fmt_run.step); 45 | } 46 | -------------------------------------------------------------------------------- /examples/events.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | // zig build-exe examples/basic.zig --pkg-begin ziglet src/index.zig --pkg-end 8 | 9 | const std = @import("std"); 10 | const ziglet = @import("ziglet"); 11 | 12 | const app = ziglet.app; 13 | 14 | const Key = app.event.Key; 15 | const Event = app.event.Event; 16 | const Window = app.Window; 17 | 18 | pub fn main() !void { 19 | var direct_allocator = std.heap.DirectAllocator.init(); 20 | var alloc = &direct_allocator.allocator; 21 | defer direct_allocator.deinit(); 22 | 23 | const opts = ziglet.app.WindowOptions{ 24 | .backend = ziglet.gfx.RenderBackend.OpenGL, 25 | .fullscreen = false, 26 | .borderless = false, 27 | .resizeable = true, 28 | .width = 512, 29 | .height = 512, 30 | .title = "hello_world", 31 | }; 32 | 33 | var w = try Window.init(alloc, opts); 34 | defer w.deinit(); 35 | 36 | while (!w.should_close) { 37 | w.update(); 38 | 39 | while (w.event_pump.pop()) |event| { 40 | switch (event) { 41 | Event.KeyDown => |key| { 42 | std.debug.warn("KeyDown: {}\n", key); 43 | switch (key) { 44 | Key.Escape => { 45 | w.should_close = true; 46 | break; 47 | }, 48 | else => continue, 49 | } 50 | }, 51 | Event.KeyUp => |key| std.debug.warn("KeyUp: {}\n", key), 52 | Event.Char => |char| std.debug.warn("Char: {}\n", char), 53 | Event.MouseDown => |btn| std.debug.warn("MouseDown: {}\n", btn), 54 | Event.MouseUp => |btn| std.debug.warn("MouseUp: {}\n", btn), 55 | Event.MouseScroll => |scroll| std.debug.warn("MouseScroll: {}, {}\n", scroll[0], scroll[1]), 56 | Event.MouseMove => |coord| std.debug.warn("MouseMove: {}, {}\n", coord[0], coord[1]), 57 | Event.MouseEnter => std.debug.warn("MouseEnter\n"), 58 | Event.MouseLeave => std.debug.warn("MouseLeave\n"), 59 | Event.Resized => |size| std.debug.warn("Resized: {}, {}\n", size[0], size[1]), 60 | Event.Iconified => std.debug.warn("Iconified\n"), 61 | Event.Restored => std.debug.warn("Restored\n"), 62 | // Event.FileDroppped => |path| std.debug.warn("FileDroppped: {}\n", path), 63 | else => { 64 | std.debug.warn("invalid event\n"); 65 | }, 66 | } 67 | } 68 | 69 | std.os.time.sleep(16666667); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const builtin = @import("builtin"); 8 | pub const event = @import("app/event.zig"); 9 | const ziglet = @import("ziglet.zig"); 10 | 11 | const Os = builtin.Os; 12 | const gfx = ziglet.gfx; 13 | 14 | pub const WindowOptions = struct { 15 | backend: gfx.RenderBackend, 16 | fullscreen: bool, 17 | borderless: bool, 18 | resizeable: bool, 19 | width: usize, 20 | height: usize, 21 | title: []const u8, 22 | }; 23 | 24 | pub const WindowError = error{ 25 | InitError, 26 | ShutdownError, 27 | }; 28 | 29 | pub use switch (builtin.os) { 30 | Os.windows => @import("app/windows.zig"), 31 | else => @compileError("unsupported os"), 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/event.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | const ziglet = @import("../ziglet.zig"); 9 | const util = @import("util.zig"); 10 | 11 | const mem = std.mem; 12 | 13 | pub const Event = union(enum) { 14 | KeyDown: Key, 15 | KeyUp: Key, 16 | Char: u8, 17 | MouseDown: MouseButton, 18 | MouseUp: MouseButton, 19 | MouseScroll: [2]f32, 20 | MouseMove: [2]f32, 21 | MouseEnter: void, 22 | MouseLeave: void, 23 | Resized: [2]i32, 24 | Iconified: void, 25 | Restored: void, 26 | // FileDroppped: []const u8, 27 | }; 28 | 29 | pub const EventPump = util.RingBuffer(Event); 30 | 31 | pub const MouseButton = enum { 32 | Left, 33 | Right, 34 | Middle, 35 | }; 36 | 37 | pub const Key = enum { 38 | Unknown, 39 | Space, 40 | Apostrophe, 41 | Comma, 42 | Minus, 43 | Period, 44 | Slash, 45 | Key0, 46 | Key1, 47 | Key2, 48 | Key3, 49 | Key4, 50 | Key5, 51 | Key6, 52 | Key7, 53 | Key8, 54 | Key9, 55 | Semicolon, 56 | Equal, 57 | A, 58 | B, 59 | C, 60 | D, 61 | E, 62 | F, 63 | G, 64 | H, 65 | I, 66 | J, 67 | K, 68 | L, 69 | M, 70 | N, 71 | O, 72 | P, 73 | Q, 74 | R, 75 | S, 76 | T, 77 | U, 78 | V, 79 | W, 80 | X, 81 | Y, 82 | Z, 83 | LeftBracket, 84 | Backslash, 85 | RightBracket, 86 | Backquote, 87 | World1, 88 | World2, 89 | Escape, 90 | Enter, 91 | Tab, 92 | Backspace, 93 | Insert, 94 | Delete, 95 | Right, 96 | Left, 97 | Down, 98 | Up, 99 | PageUp, 100 | PageDown, 101 | Home, 102 | End, 103 | CapsLock, 104 | ScrollLock, 105 | NumLock, 106 | PrintScreen, 107 | Pause, 108 | F1, 109 | F2, 110 | F3, 111 | F4, 112 | F5, 113 | F6, 114 | F7, 115 | F8, 116 | F9, 117 | F10, 118 | F11, 119 | F12, 120 | F13, 121 | F14, 122 | F15, 123 | F16, 124 | F17, 125 | F18, 126 | F19, 127 | F20, 128 | F21, 129 | F22, 130 | F23, 131 | F24, 132 | F25, 133 | Kp0, 134 | Kp1, 135 | Kp2, 136 | Kp3, 137 | Kp4, 138 | Kp5, 139 | Kp6, 140 | Kp7, 141 | Kp8, 142 | Kp9, 143 | KpDecimal, 144 | KpDivide, 145 | KpMultiply, 146 | KpSubtract, 147 | KpAdd, 148 | KpEnter, 149 | KpEqual, 150 | LeftShift, 151 | LeftControl, 152 | LeftAlt, 153 | LeftSuper, 154 | RightShift, 155 | RightControl, 156 | RightAlt, 157 | RightSuper, 158 | Menu, 159 | }; 160 | 161 | const Keyboard = struct { 162 | prev_time: u64, 163 | delta_time: f32, 164 | keys: [512]bool, 165 | keys_down_duration: [512]f32, 166 | key_repeat_delay: f32, 167 | key_repeat_rate: f32, 168 | 169 | pub fn new() Keyboard { 170 | return Keyboard{ 171 | .prev_time = 0, 172 | .delta_time = 0, 173 | .keys = []bool{false} ** 512, 174 | .keys_down_duration = []f32{-1.0} ** 512, 175 | .key_repeat_delay = 0.0, 176 | .key_repeat_rate = 0.0, 177 | }; 178 | } 179 | 180 | pub fn update(self: *Keyboard) void { 181 | const current_time = std.os.time.timestamp(); 182 | const delta_time = @intToFloat(f32, current_time - self.prev_time); 183 | self.prev_time = current_time; 184 | self.delta_time = delta_time; 185 | 186 | for (self.keys_down_duration) |*key, idx| { 187 | if (self.keys[idx]) { 188 | if (key.* < 0.0) { 189 | key.* = 0.0; 190 | } else { 191 | key.* += delta_time; 192 | } 193 | } else { 194 | key.* = -1.0; 195 | } 196 | } 197 | } 198 | 199 | pub inline fn set_key(self: *Keyboard, key: Key, state: bool) void { 200 | self.keys[@enumToInt(key)] = state; 201 | } 202 | 203 | pub fn is_down(self: *Keyboard, key: Key) bool { 204 | return self.keys[@enumToInt(key)]; 205 | } 206 | 207 | pub fn keys_down(self: *Keyboard, alloc: *std.mem.Allocator) !std.ArrayList(Key) { 208 | var result = std.ArrayList(Key).init(alloc); 209 | 210 | for (self.keys) |down, idx| { 211 | if (down) { 212 | const e = @truncate(u7, idx); 213 | try result.append(@intToEnum(Key, e)); 214 | } 215 | } 216 | 217 | return result; 218 | } 219 | 220 | pub fn was_pressed(self: *Keyboard, key: Key, repeat: bool) bool { 221 | const t = self.keys_down_duration[@enumToInt(key)]; 222 | 223 | if (t == 0.0) return true; 224 | 225 | if (repeat and (t > self.key_repeat_delay)) { 226 | const delay = self.key_repeat_delay; 227 | const rate = self.key_repeat_rate; 228 | if ((@rem(t - delay, rate) > rate * 0.5) != (@rem(t - delay - self.delta_time, rate) > rate * 0.5)) { 229 | return true; 230 | } 231 | } 232 | 233 | return false; 234 | } 235 | 236 | pub fn keys_pressed(self: *Keyboard, alloc: *std.mem.Allocator, repeat: bool) !std.ArrayList(Key) { 237 | var result = std.ArrayList(Key).init(alloc); 238 | 239 | for (self.keys) |down, idx| { 240 | const e = @truncate(u7, idx); 241 | if (self.was_pressed(@intToEnum(Key, e), repeat)) { 242 | try result.append(@intToEnum(Key, e)); 243 | } 244 | } 245 | 246 | return result; 247 | } 248 | 249 | pub fn set_key_repeat_delay(self: *Keyboard, delay: f32) void { 250 | self.key_repeat_delay = delay; 251 | } 252 | 253 | pub fn set_key_repeat_rate(self: *Keyboard, rate: f32) void { 254 | self.key_repeat_rate = rate; 255 | } 256 | }; 257 | -------------------------------------------------------------------------------- /src/app/util.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | const Allocator = std.mem.Allocator; 9 | 10 | /// max str len of 512 11 | pub fn L(str: []const u8) [512]u16 { 12 | var result = []u16{0} ** 512; 13 | const last = std.unicode.utf8ToUtf16Le(result[0..], str) catch unreachable; 14 | result[last] = 0; 15 | return result; 16 | } 17 | 18 | pub fn clamp(comptime T: type, x: T, a: T, b: T) T { 19 | return std.math.max(std.math.min(a, b), std.math.min(x, std.math.max(a, b))); 20 | } 21 | 22 | fn nextPowerOf2(x: usize) usize { 23 | if (x == 0) return 1; 24 | var result = x -% 1; 25 | result = switch (@sizeOf(usize)) { 26 | 8 => result | (result >> 32), 27 | 4 => result | (result >> 16), 28 | 2 => result | (result >> 8), 29 | 1 => result | (result >> 4), 30 | else => 0, 31 | }; 32 | result |= (result >> 4); 33 | result |= (result >> 2); 34 | result |= (result >> 1); 35 | return result +% (1 + @boolToInt(x <= 0)); 36 | } 37 | 38 | pub fn RingBuffer(comptime T: type) type { 39 | return AlignedRingBuffer(T, @alignOf(T)); 40 | } 41 | 42 | pub fn AlignedRingBuffer(comptime T: type, comptime A: u29) type { 43 | return struct { 44 | const Self = @This(); 45 | 46 | allocator: *Allocator, 47 | items: []align(A) ?T, 48 | write: usize, 49 | read: usize, 50 | 51 | pub fn init(allocator: *Allocator) Self { 52 | return Self{ 53 | .allocator = allocator, 54 | .items = []align(A) ?T{}, 55 | .write = 0, 56 | .read = 0, 57 | }; 58 | } 59 | 60 | pub fn deinit(self: *Self) void { 61 | self.allocator.free(self.items); 62 | } 63 | 64 | fn mask(self: Self, idx: usize) usize { 65 | return idx & (self.capacity() - 1); 66 | } 67 | 68 | pub fn empty(self: Self) bool { 69 | return self.write == self.read; 70 | } 71 | 72 | pub fn full(self: Self) bool { 73 | return self.count() == self.capacity(); 74 | } 75 | 76 | pub fn count(self: Self) usize { 77 | return self.write - self.read; 78 | } 79 | 80 | pub fn capacity(self: Self) usize { 81 | return self.items.len; 82 | } 83 | 84 | pub fn push(self: *Self, data: T) !void { 85 | if (self.full()) { 86 | const new_capacity = nextPowerOf2(self.capacity() + 1); 87 | self.items = try self.allocator.realloc(self.items, new_capacity); 88 | } 89 | self.items[self.mask(self.write)] = data; 90 | self.write += 1; 91 | } 92 | 93 | pub fn pop(self: *Self) ?T { 94 | if (!self.empty()) { 95 | self.read += 1; 96 | return self.items[self.mask(self.read - 1)]; 97 | } 98 | return null; 99 | } 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /src/app/windows.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | const ziglet = @import("../ziglet.zig"); 9 | const util = @import("util.zig"); 10 | const assert = std.debug.assert; 11 | 12 | const native = @import("windows/native.zig"); 13 | const mem = std.mem; 14 | 15 | pub const Event = ziglet.app.event.Event; 16 | pub const Key = ziglet.app.event.Key; 17 | pub const MouseButton = ziglet.app.event.MouseButton; 18 | 19 | const Backend = union(ziglet.gfx.RenderBackend) { 20 | OpenGL: @import("backend/opengl.zig").Context, 21 | DirectX11: @import("backend/directx.zig").Context, 22 | }; 23 | 24 | fn intToKey(lParam: native.LPARAM) Key { 25 | return switch (lParam & 0x1ff) { 26 | 0x00B => Key.Key0, 27 | 0x002 => Key.Key1, 28 | 0x003 => Key.Key2, 29 | 0x004 => Key.Key3, 30 | 0x005 => Key.Key4, 31 | 0x006 => Key.Key5, 32 | 0x007 => Key.Key6, 33 | 0x008 => Key.Key7, 34 | 0x009 => Key.Key8, 35 | 0x00A => Key.Key9, 36 | 0x01E => Key.A, 37 | 0x030 => Key.B, 38 | 0x02E => Key.C, 39 | 0x020 => Key.D, 40 | 0x012 => Key.E, 41 | 0x021 => Key.F, 42 | 0x022 => Key.G, 43 | 0x023 => Key.H, 44 | 0x017 => Key.I, 45 | 0x024 => Key.J, 46 | 0x025 => Key.K, 47 | 0x026 => Key.L, 48 | 0x032 => Key.M, 49 | 0x031 => Key.N, 50 | 0x018 => Key.O, 51 | 0x019 => Key.P, 52 | 0x010 => Key.Q, 53 | 0x013 => Key.R, 54 | 0x01F => Key.S, 55 | 0x014 => Key.T, 56 | 0x016 => Key.U, 57 | 0x02F => Key.V, 58 | 0x011 => Key.W, 59 | 0x02D => Key.X, 60 | 0x015 => Key.Y, 61 | 0x02C => Key.Z, 62 | 0x03B => Key.F1, 63 | 0x03C => Key.F2, 64 | 0x03D => Key.F3, 65 | 0x03E => Key.F4, 66 | 0x03F => Key.F5, 67 | 0x040 => Key.F6, 68 | 0x041 => Key.F7, 69 | 0x042 => Key.F8, 70 | 0x043 => Key.F9, 71 | 0x044 => Key.F10, 72 | 0x057 => Key.F11, 73 | 0x058 => Key.F12, 74 | 0x150 => Key.Down, 75 | 0x14B => Key.Left, 76 | 0x14D => Key.Right, 77 | 0x148 => Key.Up, 78 | 0x028 => Key.Apostrophe, 79 | 0x029 => Key.Backquote, 80 | 0x02B => Key.Backslash, 81 | 0x033 => Key.Comma, 82 | 0x00D => Key.Equal, 83 | 0x01A => Key.LeftBracket, 84 | 0x00C => Key.Minus, 85 | 0x034 => Key.Period, 86 | 0x01B => Key.RightBracket, 87 | 0x027 => Key.Semicolon, 88 | 0x035 => Key.Slash, 89 | 0x00E => Key.Backspace, 90 | 0x153 => Key.Delete, 91 | 0x14F => Key.End, 92 | 0x01C => Key.Enter, 93 | 0x001 => Key.Escape, 94 | 0x147 => Key.Home, 95 | 0x152 => Key.Insert, 96 | 0x15D => Key.Menu, 97 | 0x151 => Key.PageDown, 98 | 0x149 => Key.PageUp, 99 | 0x045 => Key.Pause, 100 | 0x039 => Key.Space, 101 | 0x00F => Key.Tab, 102 | 0x145 => Key.NumLock, 103 | 0x03A => Key.CapsLock, 104 | 0x046 => Key.ScrollLock, 105 | 0x02A => Key.LeftShift, 106 | 0x036 => Key.RightShift, 107 | 0x01D => Key.LeftControl, 108 | 0x11D => Key.RightControl, 109 | 0x052 => Key.Kp0, 110 | 0x04F => Key.Kp1, 111 | 0x050 => Key.Kp2, 112 | 0x051 => Key.Kp3, 113 | 0x04B => Key.Kp4, 114 | 0x04C => Key.Kp5, 115 | 0x04D => Key.Kp6, 116 | 0x047 => Key.Kp7, 117 | 0x048 => Key.Kp8, 118 | 0x049 => Key.Kp9, 119 | 0x053 => Key.KpDecimal, 120 | 0x135 => Key.KpDivide, 121 | 0x037 => Key.KpMultiply, 122 | 0x04A => Key.KpSubtract, 123 | 0x04E => Key.KpMultiply, 124 | 0x11C => Key.KpEnter, 125 | 0x038 => Key.LeftAlt, 126 | 0x138 => Key.RightAlt, 127 | else => Key.Unknown, 128 | }; 129 | } 130 | 131 | pub const Window = struct { 132 | handle: native.HWND, 133 | 134 | fullscreen: bool, 135 | borderless: bool, 136 | resizeable: bool, 137 | width: usize, 138 | height: usize, 139 | title: []const u8, 140 | 141 | mouse_tracked: bool, 142 | iconified: bool, 143 | 144 | pub should_close: bool, 145 | pub event_pump: ziglet.app.event.EventPump, 146 | 147 | fn window_resized(self: *Window) ?[2]i32 { 148 | var rect: native.RECT = undefined; 149 | if (native.GetClientRect(self.handle, &rect) == native.TRUE) { 150 | const new_width = @intCast(usize, rect.right - rect.left); 151 | const new_height = @intCast(usize, rect.bottom - rect.top); 152 | if ((new_width != self.width) or (new_height != self.height)) { 153 | self.width = new_width; 154 | self.height = new_height; 155 | return []i32{ 156 | @intCast(i32, new_width), 157 | @intCast(i32, new_height), 158 | }; 159 | } 160 | } else { 161 | self.width = 1; 162 | self.height = 1; 163 | } 164 | return null; 165 | } 166 | 167 | stdcallcc fn wnd_proc(hWnd: native.HWND, msg: native.UINT, wParam: native.WPARAM, lParam: native.LPARAM) native.LRESULT { 168 | var result: native.LRESULT = 0; 169 | 170 | var self = @intToPtr(?*Window, native.GetWindowLongPtrW(hWnd, native.GWLP_USERDATA)) orelse { 171 | return native.DefWindowProcW(hWnd, msg, wParam, lParam); 172 | }; 173 | 174 | switch (msg) { 175 | native.WM_CLOSE => { 176 | self.should_close = true; 177 | }, 178 | native.WM_SYSKEYDOWN, native.WM_KEYDOWN => { 179 | self.event_pump.push(Event{ .KeyDown = intToKey(lParam >> 16) }) catch unreachable; 180 | }, 181 | native.WM_SYSKEYUP, native.WM_KEYUP => { 182 | self.event_pump.push(Event{ .KeyUp = intToKey(lParam >> 16) }) catch unreachable; 183 | }, 184 | native.WM_SYSCHAR, native.WM_CHAR => { 185 | self.event_pump.push(Event{ .Char = @intCast(u8, wParam) }) catch unreachable; 186 | }, 187 | native.WM_LBUTTONDOWN => { 188 | self.event_pump.push(Event{ .MouseDown = MouseButton.Left }) catch unreachable; 189 | }, 190 | native.WM_RBUTTONDOWN => { 191 | self.event_pump.push(Event{ .MouseDown = MouseButton.Right }) catch unreachable; 192 | }, 193 | native.WM_MBUTTONDOWN => { 194 | self.event_pump.push(Event{ 195 | .MouseDown = MouseButton.Middle, 196 | }) catch unreachable; 197 | }, 198 | native.WM_LBUTTONUP => { 199 | self.event_pump.push(Event{ .MouseUp = MouseButton.Left }) catch unreachable; 200 | }, 201 | native.WM_RBUTTONUP => { 202 | self.event_pump.push(Event{ .MouseUp = MouseButton.Right }) catch unreachable; 203 | }, 204 | native.WM_MBUTTONUP => { 205 | self.event_pump.push(Event{ 206 | .MouseUp = MouseButton.Middle, 207 | }) catch unreachable; 208 | }, 209 | native.WM_MOUSEWHEEL => { 210 | if (self.mouse_tracked) { 211 | const scroll = @intToFloat(f32, @intCast(i16, @truncate(u16, (@truncate(u32, wParam) >> 16) & 0xffff))) * 0.1; 212 | self.event_pump.push(Event{ 213 | .MouseScroll = []f32{ 0.0, scroll }, 214 | }) catch unreachable; 215 | } 216 | }, 217 | native.WM_MOUSEHWHEEL => { 218 | if (self.mouse_tracked) { 219 | const scroll = @intToFloat(f32, @intCast(i16, @truncate(u16, (@truncate(u32, wParam) >> 16) & 0xffff))) * 0.1; 220 | self.event_pump.push(Event{ 221 | .MouseScroll = []f32{ scroll, 0.0 }, 222 | }) catch unreachable; 223 | } 224 | }, 225 | native.WM_MOUSEMOVE => { 226 | if (!self.mouse_tracked) { 227 | self.mouse_tracked = true; 228 | var tme: native.TRACKMOUSEEVENT = undefined; 229 | tme.cbSize = @sizeOf(native.TRACKMOUSEEVENT); 230 | tme.dwFlags = native.TME_LEAVE; 231 | tme.hwndTrack = self.handle; 232 | assert(native.TrackMouseEvent(&tme) != 0); 233 | self.event_pump.push(Event{ 234 | .MouseEnter = {}, 235 | }) catch unreachable; 236 | } 237 | self.event_pump.push(Event{ 238 | .MouseMove = []f32{ 239 | @bitCast(f32, native.GET_X_LPARAM(lParam)), 240 | @bitCast(f32, native.GET_Y_LPARAM(lParam)), 241 | }, 242 | }) catch unreachable; 243 | }, 244 | native.WM_MOUSELEAVE => { 245 | self.mouse_tracked = false; 246 | self.event_pump.push(Event{ 247 | .MouseLeave = {}, 248 | }) catch unreachable; 249 | }, 250 | native.WM_SIZE => { 251 | const iconified = wParam == native.SIZE_MINIMIZED; 252 | if (iconified != self.iconified) { 253 | self.iconified = iconified; 254 | if (iconified) { 255 | self.event_pump.push(Event{ 256 | .Iconified = {}, 257 | }) catch unreachable; 258 | } else { 259 | self.event_pump.push(Event{ 260 | .Restored = {}, 261 | }) catch unreachable; 262 | } 263 | } 264 | if (self.window_resized()) |new_size| { 265 | self.event_pump.push(Event{ 266 | .Resized = new_size, 267 | }) catch unreachable; 268 | } 269 | }, 270 | // native.WM_DROPFILES => { 271 | // const hDrop = @intToPtr(native.HDROP, wParam); 272 | // const count = native.DragQueryFileW(hDrop, 0xFFFFFFFF, null, 0); 273 | 274 | // var index: c_uint = 0; 275 | // while (index < count) : (index += 1) { 276 | // var in_buffer = []u16{0} ** (std.os.MAX_PATH_BYTES / 3 + 1); 277 | // var out_buffer = []u8{0} ** (std.os.MAX_PATH_BYTES + 1); 278 | // const len = native.DragQueryFileW(hDrop, index, in_buffer[0..].ptr, in_buffer.len); 279 | // _ = std.unicode.utf16leToUtf8(out_buffer[0..], in_buffer[0..]) catch unreachable; 280 | // self.event_pump.push(Event { 281 | // .FileDroppped = out_buffer[0..len], 282 | // }) catch unreachable; 283 | // } 284 | 285 | // native.DragFinish(hDrop); 286 | // }, 287 | else => { 288 | return native.DefWindowProcW(hWnd, msg, wParam, lParam); 289 | }, 290 | } 291 | 292 | return result; 293 | } 294 | 295 | fn open_window(options: ziglet.app.WindowOptions) ziglet.app.WindowError!native.HWND { 296 | const wtitle = util.L(options.title)[0..]; 297 | 298 | const wcex = native.WNDCLASSEX{ 299 | .cbSize = @sizeOf(native.WNDCLASSEX), 300 | .style = native.CS_HREDRAW | native.CS_VREDRAW | native.CS_OWNDC, 301 | .lpfnWndProc = wnd_proc, 302 | .cbClsExtra = 0, 303 | .cbWndExtra = 0, 304 | .hInstance = native.GetModuleHandleW(null), 305 | .hIcon = native.LoadIconW(null, native.IDI_WINLOGO).?, 306 | .hCursor = native.LoadCursorW(null, native.IDC_ARROW).?, 307 | .hbrBackground = null, 308 | .lpszMenuName = null, 309 | .lpszClassName = wtitle.ptr, 310 | .hIconSm = null, 311 | }; 312 | 313 | if (native.RegisterClassExW(&wcex) == 0) { 314 | return error.InitError; 315 | } 316 | 317 | var rect = native.RECT{ 318 | .left = 0, 319 | .right = @truncate(c_int, @intCast(isize, options.width)), 320 | .top = 0, 321 | .bottom = @truncate(c_int, @intCast(isize, options.height)), 322 | }; 323 | 324 | var dwExStyle: u32 = 0; 325 | var dwStyle: u32 = native.WS_CLIPSIBLINGS | native.WS_CLIPCHILDREN; 326 | 327 | if (options.fullscreen) { 328 | var dmScreenSettings: native.DEVMODEW = undefined; 329 | dmScreenSettings.dmSize = @sizeOf(native.DEVMODEW); 330 | dmScreenSettings.dmPelsWidth = @intCast(u32, options.width); 331 | dmScreenSettings.dmPelsHeight = @intCast(u32, options.height); 332 | dmScreenSettings.dmBitsPerPel = 32; 333 | dmScreenSettings.dmFields = native.DM_BITSPERPEL | native.DM_PELSWIDTH | native.DM_PELSHEIGHT; 334 | if (native.ChangeDisplaySettingsW(&dmScreenSettings, native.CDS_FULLSCREEN) != 0) { 335 | return error.InitError; 336 | } else { 337 | dwExStyle = native.WS_EX_APPWINDOW; 338 | dwStyle |= native.WS_POPUP; 339 | _ = native.ShowCursor(native.FALSE); 340 | } 341 | } else { 342 | dwExStyle = native.WS_EX_APPWINDOW | native.WS_EX_WINDOWEDGE | native.WS_EX_ACCEPTFILES; 343 | dwStyle |= native.DWORD(native.WS_OVERLAPPEDWINDOW); 344 | 345 | if (options.resizeable) { 346 | dwStyle |= native.DWORD(native.WS_THICKFRAME) | native.DWORD(native.WS_MAXIMIZEBOX); 347 | } else { 348 | dwStyle &= ~native.DWORD(native.WS_MAXIMIZEBOX); 349 | dwStyle &= ~native.DWORD(native.WS_THICKFRAME); 350 | } 351 | 352 | if (options.borderless) { 353 | dwStyle &= ~native.DWORD(native.WS_THICKFRAME); 354 | } 355 | } 356 | 357 | if (native.AdjustWindowRectEx(&rect, dwStyle, native.FALSE, dwExStyle) == 0) { 358 | return error.InitError; 359 | } 360 | 361 | rect.right -= rect.left; 362 | rect.bottom -= rect.top; 363 | 364 | const result = native.CreateWindowExW(dwExStyle, wtitle.ptr, wtitle.ptr, dwStyle, native.CW_USEDEFAULT, native.CW_USEDEFAULT, rect.right, rect.bottom, null, null, wcex.hInstance, null) orelse return error.InitError; 365 | _ = native.ShowWindow(result, native.SW_NORMAL); 366 | 367 | return result; 368 | } 369 | 370 | pub fn init(allocator: *mem.Allocator, options: ziglet.app.WindowOptions) ziglet.app.WindowError!Window { 371 | var result = Window{ 372 | .handle = undefined, 373 | .fullscreen = options.fullscreen, 374 | .borderless = options.borderless, 375 | .resizeable = options.resizeable, 376 | .width = options.width, 377 | .height = options.height, 378 | .title = options.title, 379 | .should_close = false, 380 | .mouse_tracked = false, 381 | .iconified = false, 382 | .event_pump = ziglet.app.event.EventPump.init(allocator), 383 | }; 384 | 385 | errdefer result.deinit(); 386 | 387 | result.handle = try open_window(options); 388 | 389 | return result; 390 | } 391 | 392 | pub fn deinit(self: Window) void { 393 | if (self.fullscreen) { 394 | _ = native.ChangeDisplaySettingsW(null, 0); 395 | _ = native.ShowCursor(native.TRUE); 396 | } 397 | _ = native.UnregisterClassW(util.L(self.title)[0..].ptr, native.GetModuleHandleW(null)); 398 | } 399 | 400 | fn message_loop(self: *const Window) void { 401 | var msg: native.MSG = undefined; 402 | 403 | while (native.PeekMessageW(&msg, self.handle, 0, 0, native.PM_REMOVE) == native.TRUE) { 404 | if (msg.message == native.WM_QUIT) break; 405 | _ = native.TranslateMessage(&msg); 406 | _ = native.DispatchMessageW(&msg); 407 | } 408 | } 409 | 410 | pub fn update(self: *Window) void { 411 | _ = native.SetWindowLongPtrW(self.handle, native.GWLP_USERDATA, @ptrToInt(self)); 412 | 413 | self.message_loop(); 414 | } 415 | }; 416 | -------------------------------------------------------------------------------- /src/app/windows/backend/directx11.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | -------------------------------------------------------------------------------- /src/app/windows/backend/opengl.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | 9 | const super = @import("../index.zig"); 10 | const native = @import("../native.zig"); 11 | 12 | const HGLRC = HANDLE; 13 | const PROC = *@OpaqueType(); 14 | 15 | const GLenum = u32; 16 | const GLboolean = bool; 17 | const GLbitfield = u32; 18 | const GLbyte = i8; 19 | const GLshort = i16; 20 | const GLint = i32; 21 | const GLsizei = i32; 22 | const GLubyte = u8; 23 | const GLushort = u16; 24 | const GLuint = u32; 25 | const GLfloat = f32; 26 | const GLclampf = f32; 27 | const GLdouble = f64; 28 | const GLclampd = f64; 29 | const GLvoid = *c_void; 30 | 31 | // opengl function pointers 32 | const PFNGLARRAYELEMENTEXTPROC = ?stdcallcc fn (GLint) void; 33 | const PFNGLDRAWARRAYSEXTPROC = ?stdcallcc fn (GLenum, GLint, GLsizei) void; 34 | const PFNGLVERTEXPOINTEREXTPROC = ?stdcallcc fn (GLint, GLenum, GLsizei, GLsizei, ?*const GLvoid) void; 35 | const PFNGLNORMALPOINTEREXTPROC = ?stdcallcc fn (GLenum, GLsizei, GLsizei, ?*const GLvoid) void; 36 | const PFNGLCOLORPOINTEREXTPROC = ?stdcallcc fn (GLint, GLenum, GLsizei, GLsizei, ?*const GLvoid) void; 37 | const PFNGLINDEXPOINTEREXTPROC = ?stdcallcc fn (GLenum, GLsizei, GLsizei, ?*const GLvoid) void; 38 | const PFNGLTEXCOORDPOINTEREXTPROC = ?stdcallcc fn (GLint, GLenum, GLsizei, GLsizei, ?*const GLvoid) void; 39 | const PFNGLEDGEFLAGPOINTEREXTPROC = ?stdcallcc fn (GLsizei, GLsizei, ?*const GLboolean) void; 40 | const PFNGLGETPOINTERVEXTPROC = ?stdcallcc fn (GLenum, ?*(?*GLvoid)) void; 41 | const PFNGLARRAYELEMENTARRAYEXTPROC = ?stdcallcc fn (GLenum, GLsizei, ?*const GLvoid) void; 42 | const PFNGLDRAWRANGEELEMENTSWINPROC = ?stdcallcc fn (GLenum, GLuint, GLuint, GLsizei, GLenum, ?*const GLvoid) void; 43 | const PFNGLADDSWAPHINTRECTWINPROC = ?stdcallcc fn (GLint, GLint, GLsizei, GLsizei) void; 44 | const PFNGLCOLORTABLEEXTPROC = ?stdcallcc fn (GLenum, GLenum, GLsizei, GLenum, GLenum, ?*const GLvoid) void; 45 | const PFNGLCOLORSUBTABLEEXTPROC = ?stdcallcc fn (GLenum, GLsizei, GLsizei, GLenum, GLenum, ?*const GLvoid) void; 46 | const PFNGLGETCOLORTABLEEXTPROC = ?stdcallcc fn (GLenum, GLenum, GLenum, ?*GLvoid) void; 47 | const PFNGLGETCOLORTABLEPARAMETERIVEXTPROC = ?stdcallcc fn (GLenum, GLenum, ?*GLint) void; 48 | const PFNGLGETCOLORTABLEPARAMETERFVEXTPROC = ?stdcallcc fn (GLenum, GLenum, ?*GLfloat) void; 49 | 50 | // wgl extentions 51 | const PFNWGLCREATECONTEXTATTRIBSARBPROC = ?stdcallcc fn (HDC, HGLRC, ?*const c_int) HGLRC; 52 | const PFNWGLCHOOSEPIXELFORMATARBPROC = ?stdcallcc fn (HDC, ?*const c_int, ?*const FLOAT, UINT, ?*c_int, ?*UINT) BOOL; 53 | 54 | const PIXELFORMATDESCRIPTOR = extern struct { 55 | nSize: WORD, 56 | nVersion: WORD, 57 | dwFlags: DWORD, 58 | iPixelType: BYTE, 59 | cColorBits: BYTE, 60 | cRedBits: BYTE, 61 | cRedShift: BYTE, 62 | cGreenBits: BYTE, 63 | cGreenShift: BYTE, 64 | cBlueBits: BYTE, 65 | cBlueShift: BYTE, 66 | cAlphaBits: BYTE, 67 | cAlphaShift: BYTE, 68 | cAccumBits: BYTE, 69 | cAccumRedBits: BYTE, 70 | cAccumGreenBits: BYTE, 71 | cAccumBlueBits: BYTE, 72 | cAccumAlphaBits: BYTE, 73 | cDepthBits: BYTE, 74 | cStencilBits: BYTE, 75 | cAuxBuffers: BYTE, 76 | iLayerType: BYTE, 77 | bReserved: BYTE, 78 | dwLayerMask: DWORD, 79 | dwVisibleMask: DWORD, 80 | dwDamageMask: DWORD, 81 | }; 82 | 83 | extern "gdi32" stdcallcc fn StretchDIBits(hdc: HDC, xDest: c_int, yDest: c_int, DestWidth: c_int, DestHeight: c_int, xSrc: c_int, ySrc: c_int, SrcWidth: c_int, SrcHeight: c_int, lpBits: ?*const c_void, lpbmi: ?*const BITMAPINFO, iUsage: UINT, rop: DWORD) c_int; 84 | 85 | extern "gdi32" stdcallcc fn ChoosePixelFormat(hdc: HDC, ppfd: ?*const PIXELFORMATDESCRIPTOR) c_int; 86 | 87 | extern "gdi32" stdcallcc fn SetPixelFormat(hdc: HDC, format: c_int, ppfd: ?*const PIXELFORMATDESCRIPTOR) BOOL; 88 | 89 | extern "opengl32" stdcallcc fn wglCreateContext(arg0: HDC) ?HGLRC; 90 | 91 | extern "opengl32" stdcallcc fn wglMakeCurrent(arg0: HDC, arg1: HGLRC) BOOL; 92 | 93 | extern "opengl32" stdcallcc fn wglDeleteContext(arg0: HGLRC) BOOL; 94 | 95 | extern "opengl32" stdcallcc fn wglGetProcAddress(LPCSTR) ?PROC; 96 | 97 | pub const OpenGLError = error{ 98 | InitError, 99 | ShutdownError, 100 | }; 101 | 102 | pub const Context = struct { 103 | dummy_hRC: native.HGLRC, 104 | dummy_hDc: native.HDC, 105 | dummy_pfd: PIXELFORMATDESCRIPTOR, 106 | dummy_pfdid: c_int, 107 | dummy_hWnd: native.HWND, 108 | 109 | hRC: HGLRC, 110 | hDc: native.HDC, 111 | hWnd: native.HWND, 112 | 113 | stdcallcc fn wnd_proc(hWnd: native.HWND, msg: native.UINT, wParam: native.WPARAM, lParam: native.LPARAM) native.LRESULT { 114 | return native.DefWindowProcW(hWnd, msg, wParam, lParam); 115 | } 116 | 117 | pub fn dummy_init(window: *const super.Window) !Context { 118 | var result: Context = undefined; 119 | 120 | result.dummy_hWnd = CreateWindowExW(CLASS_NAME.ptr, CLASS_NAME.ptr, WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 1, 1, null, null, native.GetModuleHandleW(null), null) orelse return error.InitError; 121 | 122 | result.dummy_hDc = GetDC(dummy_wnd); 123 | 124 | result.dummy_pfd = PIXELFORMATDESCRIPTOR{ 125 | .nSize = @sizeOf(PIXELFORMATDESCRIPTOR), 126 | .nVersion = 1, 127 | .dwFlags = PFD_DOUBLEBUFFER | PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL, 128 | .iPixelType = PFD_TYPE_RGBA, 129 | .cColorBits = 32, 130 | .cRedBits = 0, 131 | .cRedShift = 0, 132 | .cGreenBits = 0, 133 | .cGreenShift = 0, 134 | .cBlueBits = 0, 135 | .cBlueShift = 0, 136 | .cAlphaBits = 0, 137 | .cAlphaShift = 0, 138 | .cAccumBits = 0, 139 | .cAccumRedBits = 0, 140 | .cAccumGreenBits = 0, 141 | .cAccumBlueBits = 0, 142 | .cAccumAlphaBits = 0, 143 | .cDepthBits = 0, 144 | .cStencilBits = 0, 145 | .cAuxBuffers = 0, 146 | .iLayerType = PFD_MAIN_PLANE, 147 | .bReserved = 0, 148 | .dwLayerMask = 0, 149 | .dwVisibleMask = 0, 150 | .dwDamageMask = 0, 151 | }; 152 | 153 | result.dummy_pfdid = ChoosePixelFormat(result.dummy_hDc, &result.dummy_pfd); 154 | 155 | if (result.dummy_pfdid == 0) { 156 | return error.InitError; 157 | } 158 | 159 | if (SetPixelFormat(result.dummy_hDc, result.dummy_hDc, &result.dummy_pfd) == FALSE) { 160 | return error.InitError; 161 | } 162 | 163 | if (wglCreateContext(result.dummy_hDc)) |hRc| { 164 | result.dummy_hRc = hRc; 165 | } else return error.InitError; 166 | 167 | if (wglMakeCurrent(result.dummy_hDc, result.dummy_hRc) == FALSE) { 168 | return error.InitError; 169 | } 170 | 171 | return result; 172 | } 173 | 174 | fn dummy_deinit(self: Context) !void { 175 | if (wglMakeCurrent(null, null) == FALSE) { 176 | return error.InitError; 177 | } 178 | if (wglDeleteContext(self.dummy_hRC)) { 179 | return error.InitError; 180 | } 181 | if (ReleaseDC(self.dummy_hWnd, self.dummy_hDc) == FALSE) { 182 | return error.InitError; 183 | } 184 | if (DestroyWindow(self.dummy_hWnd) == FALSE) { 185 | return error.InitError; 186 | } 187 | } 188 | 189 | pub fn init(self: *Context, window: *const super.Window) !void { 190 | self.hWnd = window.hWnd; 191 | self.dummy_init(window); 192 | 193 | self.dummy_deinit(); 194 | 195 | if (wglMakeCurrent(self.hDc, self.hRc) == FALSE) { 196 | return error.InitError; 197 | } 198 | } 199 | 200 | pub fn deinit(self: Context) !void { 201 | if (wglMakeCurrent(null, null) == FALSE) { 202 | return error.ShutdownError; 203 | } 204 | if (wglDeleteContext(self.hRC)) { 205 | return error.ShutdownError; 206 | } 207 | if (ReleaseDC(self.hWnd, self.hDc) == FALSE) { 208 | return error.ShutdownError; 209 | } 210 | if (DestroyWindow(self.hWnd) == FALSE) { 211 | return error.ShutdownError; 212 | } 213 | if (UnregisterClassW(CLASS_NAME.ptr, self.hInstace)) { 214 | return error.ShutdownError; 215 | } 216 | } 217 | }; 218 | -------------------------------------------------------------------------------- /src/app/windows/native.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | 9 | pub use std.os.windows; 10 | 11 | pub const ATOM = u16; 12 | 13 | pub const LONG_PTR = usize; 14 | 15 | pub const HWND = HANDLE; 16 | 17 | pub const HICON = HANDLE; 18 | 19 | pub const HCURSOR = HANDLE; 20 | 21 | pub const HBRUSH = HANDLE; 22 | 23 | pub const HMENU = HANDLE; 24 | 25 | pub const HDC = HANDLE; 26 | 27 | pub const HDROP = HANDLE; 28 | 29 | pub const LPARAM = LONG_PTR; 30 | 31 | pub const WPARAM = LONG_PTR; 32 | 33 | pub const LRESULT = LONG_PTR; 34 | 35 | pub const MAKEINTRESOURCE = [*]WCHAR; 36 | 37 | pub const CS_VREDRAW = 1; 38 | 39 | pub const CS_HREDRAW = 2; 40 | 41 | pub const CS_OWNDC = 32; 42 | 43 | pub const WM_DESTROY = 2; 44 | 45 | pub const WM_SIZE = 5; 46 | 47 | pub const WM_CLOSE = 16; 48 | 49 | pub const WM_QUIT = 18; 50 | 51 | pub const WM_KEYDOWN = 256; 52 | 53 | pub const WM_KEYUP = 257; 54 | 55 | pub const WM_CHAR = 258; 56 | 57 | pub const WM_SYSCHAR = 262; 58 | 59 | pub const WM_SYSKEYDOWN = 260; 60 | 61 | pub const WM_SYSKEYUP = 261; 62 | 63 | pub const WM_MOUSEMOVE = 512; 64 | 65 | pub const WM_LBUTTONDOWN = 513; 66 | 67 | pub const WM_LBUTTONUP = 514; 68 | 69 | pub const WM_RBUTTONDOWN = 516; 70 | 71 | pub const WM_RBUTTONUP = 517; 72 | 73 | pub const WM_MBUTTONDOWN = 519; 74 | 75 | pub const WM_MBUTTONUP = 520; 76 | 77 | pub const WM_MOUSEWHEEL = 522; 78 | 79 | pub const WM_MOUSEHWHEEL = 526; 80 | 81 | pub const WM_DROPFILES = 563; 82 | 83 | pub const WM_MOUSELEAVE = 675; 84 | 85 | pub const IDC_ARROW = @intToPtr(MAKEINTRESOURCE, 32512); 86 | 87 | pub const IDI_WINLOGO = @intToPtr(MAKEINTRESOURCE, 32517); 88 | 89 | pub const CW_USEDEFAULT = -1; 90 | 91 | pub const WS_CAPTION = 0x00C00000; 92 | 93 | pub const WS_OVERLAPPEDWINDOW = 0x00CF0000; 94 | 95 | pub const WS_MAXIMIZEBOX = 0x00010000; 96 | 97 | pub const WS_POPUP = 0x80000000; 98 | 99 | pub const WS_SYSMENU = 0x00080000; 100 | 101 | pub const WS_THICKFRAME = 0x00040000; 102 | 103 | pub const WS_CLIPCHILDREN = 0x02000000; 104 | 105 | pub const WS_CLIPSIBLINGS = 0x04000000; 106 | 107 | pub const WS_EX_ACCEPTFILES = 0x00000010; 108 | 109 | pub const WS_EX_APPWINDOW = 0x00040000; 110 | 111 | pub const WS_EX_WINDOWEDGE = 0x00000100; 112 | 113 | pub const SW_NORMAL = 1; 114 | 115 | pub const SWP_NOSIZE = 0x0001; 116 | 117 | pub const SWP_SHOWWINDOW = 0x0040; 118 | 119 | pub const PM_REMOVE = 1; 120 | 121 | pub const VK_ESCAPE = 27; 122 | 123 | pub const GWLP_USERDATA = -21; 124 | 125 | pub const BI_BITFIELDS = 3; 126 | 127 | pub const DIB_RGB_COLORS = 0; 128 | 129 | pub const SRCCOPY = 0x00CC0020; 130 | 131 | pub const PFD_DOUBLEBUFFER = 0x00000001; 132 | 133 | pub const PFD_DRAW_TO_WINDOW = 0x00000004; 134 | 135 | pub const PFD_SUPPORT_OPENGL = 0x00000020; 136 | 137 | pub const PFD_TYPE_RGBA = 0; 138 | 139 | pub const PFD_MAIN_PLANE = 0; 140 | 141 | pub const DM_PELSWIDTH = 0x00080000; 142 | 143 | pub const DM_PELSHEIGHT = 0x00100000; 144 | 145 | pub const DM_BITSPERPEL = 0x00040000; 146 | 147 | pub const CDS_FULLSCREEN = 4; 148 | 149 | pub const SIZE_MINIMIZED = 1; 150 | 151 | pub const SIZE_MAXIMIZED = 2; 152 | 153 | pub const TME_LEAVE = 2; 154 | 155 | pub const WNDPROC = stdcallcc fn (HWND, UINT, WPARAM, LPARAM) LRESULT; 156 | 157 | pub const WNDCLASSEX = extern struct { 158 | cbSize: UINT, 159 | style: UINT, 160 | lpfnWndProc: ?WNDPROC, 161 | cbClsExtra: c_int, 162 | cbWndExtra: c_int, 163 | hInstance: HMODULE, 164 | hIcon: ?HICON, 165 | hCursor: ?HCURSOR, 166 | hbrBackground: ?HBRUSH, 167 | lpszMenuName: ?[*]const WCHAR, 168 | lpszClassName: [*]const WCHAR, 169 | hIconSm: ?HICON, 170 | }; 171 | 172 | pub const RECT = extern struct { 173 | left: LONG, 174 | top: LONG, 175 | right: LONG, 176 | bottom: LONG, 177 | }; 178 | 179 | pub const POINT = extern struct { 180 | x: LONG, 181 | y: LONG, 182 | }; 183 | 184 | pub const MSG = extern struct { 185 | hWnd: HWND, 186 | message: UINT, 187 | wParam: WPARAM, 188 | lParam: LPARAM, 189 | time: DWORD, 190 | pt: POINT, 191 | }; 192 | 193 | // pub const BITMAPINFOHEADER = extern struct { 194 | // biSize: DWORD, 195 | // biWidth: LONG, 196 | // biHeight: LONG, 197 | // biPlanes: WORD, 198 | // biBitCount: WORD, 199 | // biCompression: DWORD, 200 | // biSizeImage: DWORD, 201 | // biXPelsPerMeter: LONG, 202 | // biYPelsPerMeter: LONG, 203 | // biClrUsed: DWORD, 204 | // biClrImportant: DWORD, 205 | // }; 206 | 207 | // pub const RGBQUAD = extern struct { 208 | // rgbBlue: BYTE, 209 | // rgbGreen: BYTE, 210 | // rgbRed: BYTE, 211 | // rgbReserved: BYTE, 212 | // }; 213 | 214 | // pub const BITMAPINFO = extern struct { 215 | // bmiHeader: BITMAPINFOHEADER, 216 | // bmiColors: [3]RGBQUAD, 217 | // }; 218 | 219 | pub const DEVMODEW = extern struct { 220 | dmDeviceName: [32]WCHAR, 221 | dmSpecVersion: WORD, 222 | dmDriverVersion: WORD, 223 | dmSize: WORD, 224 | dmDriverExtra: WORD, 225 | dmFields: DWORD, 226 | dmOrientation: c_short, 227 | dmPaperSize: c_short, 228 | dmPaperLength: c_short, 229 | dmPaperWidth: c_short, 230 | dmScale: c_short, 231 | dmCopies: c_short, 232 | dmDefaultSource: c_short, 233 | dmPrintQuality: c_short, 234 | dmColor: c_short, 235 | dmDuplex: c_short, 236 | dmYResolution: c_short, 237 | dmTTOption: c_short, 238 | dmCollate: c_short, 239 | dmFormName: [32]WCHAR, 240 | dmLogPixels: WORD, 241 | dmBitsPerPel: DWORD, 242 | dmPelsWidth: DWORD, 243 | dmPelsHeight: DWORD, 244 | dmDisplayFlags: DWORD, 245 | dmDisplayFrequency: DWORD, 246 | dmICMMethod: DWORD, 247 | dmICMIntent: DWORD, 248 | dmMediaType: DWORD, 249 | dmDitherType: DWORD, 250 | dmReserved1: DWORD, 251 | dmReserved2: DWORD, 252 | dmPanningWidth: DWORD, 253 | dmPanningHeight: DWORD, 254 | }; 255 | 256 | pub const TRACKMOUSEEVENT = extern struct { 257 | cbSize: DWORD, 258 | dwFlags: DWORD, 259 | hwndTrack: HWND, 260 | dwHoverTime: DWORD, 261 | }; 262 | 263 | pub extern "user32" stdcallcc fn LoadCursorW(hInstance: ?HINSTANCE, lpCursorName: LPCWSTR) ?HCURSOR; 264 | 265 | pub extern "user32" stdcallcc fn LoadIconW(hInstance: ?HINSTANCE, lpIconName: LPCWSTR) ?HICON; 266 | 267 | pub extern "user32" stdcallcc fn RegisterClassExW(lpWndClassEx: *const WNDCLASSEX) ATOM; 268 | 269 | pub extern "user32" stdcallcc fn UnregisterClassW(lpClassName: LPCWSTR, hInstance: HMODULE) BOOL; 270 | 271 | pub extern "user32" stdcallcc fn AdjustWindowRect(lpRect: *RECT, dwStyle: DWORD, bMenu: BOOL) BOOL; 272 | 273 | pub extern "user32" stdcallcc fn GetClientRect(wnd: HWND, lpRect: *RECT) BOOL; 274 | 275 | pub extern "user32" stdcallcc fn CreateWindowExW(dwExStyle: DWORD, lpClassName: LPCWSTR, lpWindowName: LPCWSTR, dwStyle: DWORD, X: c_int, Y: c_int, nWidth: c_int, nHeight: c_int, hWndParent: ?HWND, menu: ?HMENU, hInstance: ?HMODULE, lpParam: ?LPVOID) ?HWND; 276 | 277 | pub extern "user32" stdcallcc fn DestroyWindow(wnd: HWND) BOOL; 278 | 279 | pub extern "user32" stdcallcc fn ShowWindow(wnd: HWND, nCmdShow: c_int) BOOL; 280 | 281 | pub extern "user32" stdcallcc fn GetDC(wnd: HWND) ?HDC; 282 | 283 | pub extern "user32" stdcallcc fn ReleaseDC(wnd: HWND, hDC: HDC) c_int; 284 | 285 | pub extern "user32" stdcallcc fn PeekMessageW(lpMsg: *MSG, wnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) BOOL; 286 | 287 | pub extern "user32" stdcallcc fn SendMessageW(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) LRESULT; 288 | 289 | pub extern "user32" stdcallcc fn TranslateMessage(lpMsg: *MSG) BOOL; 290 | 291 | pub extern "user32" stdcallcc fn DispatchMessageW(lpMsg: *MSG) LONG; 292 | 293 | pub extern "user32" stdcallcc fn DefWindowProcW(wnd: HWND, msg: UINT, wp: WPARAM, lp: LPARAM) LRESULT; 294 | 295 | pub extern "user32" stdcallcc fn GetWindowLongPtrW(hWnd: HWND, nIndex: c_int) LONG_PTR; 296 | 297 | pub extern "user32" stdcallcc fn SetWindowLongPtrW(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) LONG_PTR; 298 | 299 | pub extern "user32" stdcallcc fn SetWindowTextW(hWnd: HWND, lpString: LPCWSTR) BOOL; 300 | 301 | // pub extern "user32" stdcallcc fn SetWindowPos(hWnd: HWND, hWndInsertAfter: ?HWND, 302 | // X: c_int, Y: c_int, cx: c_int, cy: c_int, uFlags: UINT) BOOL; 303 | 304 | // pub extern "user32" stdcallcc fn GetForegroundWindow() ?HWND; 305 | 306 | // pub extern "user32" stdcallcc fn ValidateRect(hWnd: HWND, lpRect: ?*const RECT) BOOL; 307 | 308 | // pub extern "user32" stdcallcc fn InvalidateRect(hWnd: HWND, lpRect: ?*const RECT, bErase: BOOL) BOOL; 309 | 310 | pub extern "user32" stdcallcc fn ChangeDisplaySettingsW(lpDevMode: ?*DEVMODEW, dwFlags: DWORD) LONG; 311 | 312 | pub extern "user32" stdcallcc fn ShowCursor(bShow: BOOL) c_int; 313 | 314 | pub extern "user32" stdcallcc fn AdjustWindowRectEx(lpRect: *RECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) BOOL; 315 | 316 | pub extern "user32" stdcallcc fn TrackMouseEvent(lpEventTrack: *TRACKMOUSEEVENT) BOOL; 317 | 318 | pub extern "shell32" stdcallcc fn DragQueryFileW(hDrop: HDROP, iFile: UINT, lpszFile: ?LPCWSTR, cch: UINT) UINT; 319 | 320 | pub extern "shell32" stdcallcc fn DragFinish(hDrop: HDROP) void; 321 | 322 | pub extern "user32" stdcallcc fn GetCursorPos(lpPoint: *POINT) BOOL; 323 | 324 | pub extern "user32" stdcallcc fn ScreenToClient(hWndl: HWND, lpPoint: *POINT) BOOL; 325 | 326 | pub inline fn LOWORD(l: DWORD) WORD { 327 | return @intCast(WORD, (l & 0xffff)); 328 | } 329 | 330 | pub inline fn HIWORD(l: DWORD) WORD { 331 | return @intCast(WORD, ((l >> 16) & 0xffff)); 332 | } 333 | 334 | pub inline fn LOBYTE(l: WORD) BYTE { 335 | return @intCast(BYTE, (l & 0xff)); 336 | } 337 | 338 | pub inline fn HIBYTE(l: WORD) BYTE { 339 | return @intCast(BYTE, ((l >> 8) & 0xff)); 340 | } 341 | 342 | pub inline fn GET_X_LPARAM(lp: LPARAM) c_int { 343 | return @intCast(c_int, @truncate(c_ushort, LOWORD(@intCast(DWORD, lp)))); 344 | } 345 | 346 | pub inline fn GET_Y_LPARAM(lp: LPARAM) c_int { 347 | return @intCast(c_int, @truncate(c_ushort, HIWORD(@intCast(DWORD, lp)))); 348 | } 349 | -------------------------------------------------------------------------------- /src/audio.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | const builtin = @import("builtin"); 9 | const time = std.os.time; 10 | 11 | const winmm = @import("audio/backend/winmm.zig"); 12 | 13 | pub const Backend = enum { 14 | Wasapi, 15 | Winmm, 16 | Null, 17 | }; 18 | 19 | pub const PlayerError = error{}; 20 | 21 | pub const AudioMode = union(enum) { 22 | const Self = @This(); 23 | 24 | Mono: usize, 25 | Stereo: usize, 26 | 27 | pub fn channelCount(self: Self) usize { 28 | return switch (self) { 29 | AudioMode.Mono => usize(1), 30 | AudioMode.Stereo => usize(2), 31 | }; 32 | } 33 | }; 34 | 35 | pub const Player = struct { 36 | const Self = @This(); 37 | 38 | player: sys.Player, 39 | pub sample_rate: usize, 40 | mode: AudioMode, 41 | buf_size: usize, 42 | 43 | pub fn new(allocator: *std.mem.Allocator, sample_rate: usize, mode: AudioMode, buf_size: usize) !Self { 44 | return Self{ 45 | .player = try sys.Player.new(allocator, sample_rate, mode, buf_size), 46 | .sample_rate = sample_rate, 47 | .buf_size = buf_size, 48 | .mode = mode, 49 | }; 50 | } 51 | 52 | fn bytes_per_sec(self: Self) usize { 53 | return self.sample_rate * switch (self.mode) { 54 | AudioMode.Mono => |bps| bps * self.mode.channelCount(), 55 | AudioMode.Stereo => |bps| bps * self.mode.channelCount(), 56 | }; 57 | } 58 | 59 | pub fn write(self: *Self, bytes: []const u8) Error!void { 60 | var written: usize = 0; 61 | var data = bytes; 62 | 63 | while (data.len > 0) { 64 | const n = try self.player.write(data); 65 | written += n; 66 | 67 | data = data[n..]; 68 | 69 | if (data.len > 0) { 70 | time.sleep(time.ns_per_s * self.buf_size / self.bytes_per_sec() / 8); 71 | } 72 | } 73 | } 74 | 75 | pub fn close(self: *Self) !void { 76 | time.sleep(time.ns_per_s * self.buf_size / self.bytes_per_sec()); 77 | try self.player.close(); 78 | } 79 | }; 80 | 81 | // test "Player -- raw audio" { 82 | // var direct_allocator = std.heap.DirectAllocator.init(); 83 | // const alloc = &direct_allocator.allocator; 84 | // defer direct_allocator.deinit(); 85 | 86 | // const mode = AudioMode { .Stereo = 2 }; 87 | // var player = try Player.new(alloc, 44100, mode, 2048); 88 | // var stream = player.outStream().stream; 89 | 90 | // var timer = try time.Timer.start(); 91 | // const duration = time.ns_per_s * 5; 92 | // const dt = 1.0 / @intToFloat(f32, player.sample_rate); 93 | 94 | // while (timer.read() < duration) { 95 | // try player.write([]u8{127}**2048); 96 | // // var i: usize = 0; 97 | // // while (i < player.buf_size) : (i += 1) { 98 | // // const p = @intToFloat(f32, i) / @intToFloat(f32, player.buf_size); 99 | // // const out = std.math.sin(p * 2.0 * std.math.pi); 100 | // // try stream.writeByte(@floatToInt(u8, out)); 101 | // // try stream.writeByte(@floatToInt(u8, out)); 102 | // // } 103 | // } 104 | // } 105 | -------------------------------------------------------------------------------- /src/audio/backend/winnm.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | const Allocator = std.mem.Allocator; 9 | const windows = std.os.windows; 10 | 11 | const WAVE_FORMAT_PCM = 0x01; 12 | const WHDR_INQUEUE = 0x10; 13 | const WAVE_MAPPER = 0xffffffff; 14 | const CALLBACK_NULL = 0x0; 15 | const HWAVEOUT = windows.HANDLE; 16 | 17 | const UINT_PTR = usize; 18 | 19 | const MMError = error{ 20 | Error, 21 | BadDeviceID, 22 | Allocated, 23 | InvalidHandle, 24 | NoDriver, 25 | NoMem, 26 | BadFormat, 27 | StillPlaying, 28 | Unprepared, 29 | Sync, 30 | }; 31 | 32 | const MMRESULT = extern enum(u32) { 33 | MMSYSERR_NOERROR = 0, 34 | MMSYSERR_ERROR = 1, 35 | MMSYSERR_BADDEVICEID = 2, 36 | MMSYSERR_ALLOCATED = 4, 37 | MMSYSERR_INVALIDHANDLE = 5, 38 | MMSYSERR_NODRIVER = 6, 39 | MMSYSERR_NOMEM = 7, 40 | WAVERR_BADFORMAT = 32, 41 | WAVERR_STILLPLAYING = 33, 42 | WAVERR_UNPREPARED = 34, 43 | WAVERR_SYNC = 35, 44 | 45 | pub fn toError(self: MMRESULT) MMError!void { 46 | return switch (self) { 47 | MMRESULT.MMSYSERR_NOERROR => {}, 48 | MMRESULT.MMSYSERR_ERROR => MMError.Error, 49 | MMRESULT.MMSYSERR_BADDEVICEID => MMError.BadDeviceID, 50 | MMRESULT.MMSYSERR_ALLOCATED => MMError.Allocated, 51 | MMRESULT.MMSYSERR_INVALIDHANDLE => MMError.InvalidHandle, 52 | MMRESULT.MMSYSERR_NODRIVER => MMError.NoDriver, 53 | MMRESULT.MMSYSERR_NOMEM => MMError.NoMem, 54 | MMRESULT.WAVERR_BADFORMAT => MMError.BadFormat, 55 | MMRESULT.WAVERR_STILLPLAYING => MMError.StillPlaying, 56 | MMRESULT.WAVERR_UNPREPARED => MMError.Unprepared, 57 | MMRESULT.WAVERR_SYNC => MMError.Sync, 58 | }; 59 | } 60 | }; 61 | 62 | const WAVEHDR = extern struct { 63 | lpData: windows.LPSTR, 64 | dwBufferLength: windows.DWORD, 65 | dwBytesRecorded: windows.DWORD, 66 | dwUser: windows.DWORD_PTR, 67 | dwFlags: windows.DWORD, 68 | dwLoops: windows.DWORD, 69 | lpNext: ?*WAVEHDR, 70 | reserved: windows.DWORD_PTR, 71 | }; 72 | 73 | const WAVEFORMATEX = extern struct { 74 | wFormatTag: windows.WORD, 75 | nChannels: windows.WORD, 76 | nSamplesPerSec: windows.DWORD, 77 | nAvgBytesPerSec: windows.DWORD, 78 | nBlockAlign: windows.WORD, 79 | wBitsPerSample: windows.WORD, 80 | cbSize: windows.WORD, 81 | }; 82 | 83 | extern "winmm" stdcallcc fn waveOutOpen(phwo: *HWAVEOUT, uDeviceID: UINT_PTR, pwfx: *const WAVEFORMATEX, dwCallback: windows.DWORD_PTR, dwCallbackInstance: windows.DWORD_PTR, fdwOpen: windows.DWORD) MMRESULT; 84 | 85 | extern "winmm" stdcallcc fn waveOutClose(hwo: HWAVEOUT) MMRESULT; 86 | 87 | extern "winmm" stdcallcc fn waveOutPrepareHeader(hwo: HWAVEOUT, pwh: *WAVEHDR, cbwh: windows.UINT) MMRESULT; 88 | 89 | extern "winmm" stdcallcc fn waveOutUnprepareHeader(hwo: HWAVEOUT, pwh: *WAVEHDR, cbwh: windows.UINT) MMRESULT; 90 | 91 | extern "winmm" stdcallcc fn waveOutWrite(hwo: HWAVEOUT, pwh: *WAVEHDR, cbwh: windows.UINT) MMRESULT; 92 | 93 | const Buffer = @import("../buffer.zig").Buffer; 94 | const AudioMode = @import("../../audio.zig").AudioMode; 95 | 96 | const Header = struct { 97 | const Self = @This(); 98 | 99 | pub const Error = error{InvalidLength}; 100 | 101 | buffer: Buffer(i16), 102 | wave_out: HWAVEOUT, 103 | wave_hdr: winnm.WAVEHDR, 104 | 105 | pub fn new(player: *Player, buf_size: usize) !Self { 106 | var result: Self = undefined; 107 | 108 | result.buffer = try Buffer(i16).initSize(player.allocator, buf_size); 109 | result.wave_out = player.wave_out; 110 | 111 | result.wave_hdr.dwBufferLength = @intCast(windows.DWORD, buf_size); 112 | result.wave_hdr.lpData = result.buffer.ptr(); 113 | result.wave_hdr.dwFlags = 0; 114 | 115 | try winnm.waveOutPrepareHeader(result.wave_out, &result.wave_hdr, @sizeOf(winnm.WAVEHDR)).toError(); 116 | 117 | return result; 118 | } 119 | 120 | pub fn write(self: *Self, data: []const i16) !void { 121 | if (data.len != self.buffer.len()) { 122 | return error.InvalidLength; 123 | } 124 | 125 | try self.buffer.replaceContents(data); 126 | 127 | try winnm.waveOutWrite(self.wave_out, &self.wave_hdr, @intCast(windows.UINT, self.buffer.len())).toError(); 128 | } 129 | 130 | pub fn destroy(self: *Self) !void { 131 | try winnm.waveOutUnprepareHeader(sefl.wave_out, &self.wave_hdr, @sizeOf(winnm.WAVEHDR)).toError(); 132 | self.buffer.deinit(); 133 | } 134 | }; 135 | 136 | pub const Backend = struct { 137 | const BUF_COUNT = 2; 138 | 139 | allocator: *Allocator, 140 | wave_out: winnm.HWAVEOUT, 141 | headers: [BUF_COUNT]Header, 142 | buffer: Buffer(f32), 143 | 144 | pub fn init(allocator: *Allocator, sample_rate: usize, mode: AudioMode, buf_size: usize) Error!Self { 145 | var result: Self = undefined; 146 | var handle: windows.HANDLE = undefined; 147 | 148 | const bps = switch (mode) { 149 | AudioMode.Mono => |bps| bps, 150 | AudioMode.Stereo => |bps| bps, 151 | }; 152 | 153 | const block_align = bps * mode.channelCount(); 154 | 155 | const format = winnm.WAVEFORMATEX{ 156 | .wFormatTag = winnm.WAVE_FORMAT_PCM, 157 | .nChannels = @intCast(windows.WORD, mode.channelCount()), 158 | .nSamplesPerSec = @intCast(windows.DWORD, sample_rate), 159 | .nAvgBytesPerSec = @intCast(windows.DWORD, sample_rate * block_align), 160 | .nBlockAlign = @intCast(windows.WORD, block_align), 161 | .wBitsPerSample = @intCast(windows.WORD, bps * 8), 162 | .cbSize = 0, 163 | }; 164 | 165 | try winnm.waveOutOpen(&handle, winnm.WAVE_MAPPER, &format, 0, 0, winnm.CALLBACK_NULL).toError(); 166 | 167 | result = Self{ 168 | .handle = handle, 169 | .headers = []Header{undefined} ** BUF_COUNT, 170 | .buf_size = buf_size, 171 | .allocator = allocator, 172 | .tmp = try Buffer(u8).initSize(allocator, buf_size), 173 | }; 174 | 175 | for (result.headers) |*header| { 176 | header.* = try Header.new(result, buf_size); 177 | } 178 | 179 | return result; 180 | } 181 | 182 | pub fn write(self: *Self, data: []const u8) Error!usize { 183 | const n = std.math.min(data.len, std.math.max(0, self.buf_size - self.tmp.len())); 184 | try self.tmp.append(data[0..n]); 185 | if (self.tmp.len() < self.buf_size) { 186 | return n; 187 | } 188 | 189 | var header = for (self.headers) |header| { 190 | if (header.wavehdr.dwFlags & winnm.WHDR_INQUEUE == 0) { 191 | break header; 192 | } 193 | } else return n; 194 | 195 | try header.write(self, self.tmp.toSlice()); 196 | try self.tmp.resize(0); 197 | 198 | return n; 199 | } 200 | 201 | pub fn close(self: *Self) Error!void { 202 | try winmm.waveOutReset(self.wave_out).toError(); 203 | 204 | for (self.headers) |*header| { 205 | try header.destroy(); 206 | } 207 | 208 | try winnm.waveOutClose(self.handle).toError(); 209 | self.buffer.deinit(); 210 | } 211 | }; 212 | -------------------------------------------------------------------------------- /src/audio/buffer.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const std = @import("std"); 8 | const debug = std.debug; 9 | const mem = std.mem; 10 | const Allocator = mem.Allocator; 11 | const assert = debug.assert; 12 | const ArrayList = std.ArrayList; 13 | 14 | /// A buffer that allocates memory and maintains a null byte at the end. 15 | pub fn Buffer(comptime T: type) type { 16 | return struct { 17 | const Self = @This(); 18 | 19 | list: ArrayList(T), 20 | 21 | /// Must deinitialize with deinit. 22 | pub fn init(allocator: *Allocator, m: []const T) !Self { 23 | var self = try initSize(allocator, m.len); 24 | mem.copy(T, self.list.items, m); 25 | return self; 26 | } 27 | 28 | /// Must deinitialize with deinit. 29 | pub fn initSize(allocator: *Allocator, size: usize) !Self { 30 | var self = initNull(allocator); 31 | try self.resize(size); 32 | return self; 33 | } 34 | 35 | /// Must deinitialize with deinit. 36 | /// None of the other operations are valid until you do one of these: 37 | /// * ::replaceContents 38 | /// * ::resize 39 | pub fn initNull(allocator: *Allocator) Self { 40 | return Self{ .list = ArrayList(T).init(allocator) }; 41 | } 42 | 43 | /// Must deinitialize with deinit. 44 | pub fn initFromSelf(buffer: *const Self) !Self { 45 | return Self.init(buffer.list.allocator, buffer.toSliceConst()); 46 | } 47 | 48 | /// Self takes ownership of the passed in slice. The slice must have been 49 | /// allocated with `allocator`. 50 | /// Must deinitialize with deinit. 51 | pub fn fromOwnedSlice(allocator: *Allocator, slice: []T) !Self { 52 | var self = Self{ .list = ArrayList(T).fromOwnedSlice(allocator, slice) }; 53 | try self.list.append(0); 54 | return self; 55 | } 56 | 57 | /// The caller owns the returned memory. The Self becomes null and 58 | /// is safe to `deinit`. 59 | pub fn toOwnedSlice(self: *Self) []T { 60 | const allocator = self.list.allocator; 61 | const result = allocator.shrink(T, self.list.items, self.len()); 62 | self.* = initNull(allocator); 63 | return result; 64 | } 65 | 66 | pub fn allocPrint(allocator: *Allocator, comptime format: []const T, args: ...) !Self { 67 | const countSize = struct { 68 | fn countSize(size: *usize, bytes: []const T) (error{}!void) { 69 | size.* += bytes.len; 70 | } 71 | }.countSize; 72 | var size: usize = 0; 73 | std.fmt.format(&size, error{}, countSize, format, args) catch |err| switch (err) {}; 74 | var self = try Self.initSize(allocator, size); 75 | assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); 76 | return self; 77 | } 78 | 79 | pub fn deinit(self: *Self) void { 80 | self.list.deinit(); 81 | } 82 | 83 | pub fn toSlice(self: *const Self) []T { 84 | return self.list.toSlice()[0..self.len()]; 85 | } 86 | 87 | pub fn toSliceConst(self: *const Self) []const T { 88 | return self.list.toSliceConst()[0..self.len()]; 89 | } 90 | 91 | pub fn shrink(self: *Self, new_len: usize) void { 92 | assert(new_len <= self.len()); 93 | self.list.shrink(new_len + 1); 94 | self.list.items[self.len()] = 0; 95 | } 96 | 97 | pub fn resize(self: *Self, new_len: usize) !void { 98 | try self.list.resize(new_len + 1); 99 | self.list.items[self.len()] = 0; 100 | } 101 | 102 | pub fn isNull(self: *const Self) bool { 103 | return self.list.len == 0; 104 | } 105 | 106 | pub fn len(self: *const Self) usize { 107 | return self.list.len - 1; 108 | } 109 | 110 | pub fn append(self: *Self, m: []const T) !void { 111 | const old_len = self.len(); 112 | try self.resize(old_len + m.len); 113 | mem.copy(T, self.list.toSlice()[old_len..], m); 114 | } 115 | 116 | pub fn appendByte(self: *Self, byte: T) !void { 117 | const old_len = self.len(); 118 | try self.resize(old_len + 1); 119 | self.list.toSlice()[old_len] = byte; 120 | } 121 | 122 | pub fn eql(self: *const Self, m: []const T) bool { 123 | return mem.eql(T, self.toSliceConst(), m); 124 | } 125 | 126 | pub fn startsWith(self: *const Self, m: []const T) bool { 127 | if (self.len() < m.len) return false; 128 | return mem.eql(T, self.list.items[0..m.len], m); 129 | } 130 | 131 | pub fn endsWith(self: *const Self, m: []const T) bool { 132 | const l = self.len(); 133 | if (l < m.len) return false; 134 | const start = l - m.len; 135 | return mem.eql(T, self.list.items[start..l], m); 136 | } 137 | 138 | pub fn replaceContents(self: *Self, m: []const T) !void { 139 | try self.resize(m.len); 140 | mem.copy(T, self.list.toSlice(), m); 141 | } 142 | 143 | /// For passing to C functions. 144 | pub fn ptr(self: *const Self) [*]T { 145 | return self.list.items.ptr; 146 | } 147 | }; 148 | } 149 | -------------------------------------------------------------------------------- /src/audio/mixer.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | // [~] const char* cm_get_error(void); 8 | // [x] void cm_init(int samplerate); 9 | // [~] void cm_set_lock(cm_EventHandler lock); 10 | // [x] void cm_set_master_gain(double gain); 11 | // [ ] void cm_process(cm_Int16 *dst, int len); 12 | // [ ] cm_Source* cm_new_source(const cm_SourceInfo *info); 13 | // [ ] cm_Source* cm_new_source_from_file(const char *filename); 14 | // [ ] cm_Source* cm_new_source_from_mem(void *data, int size); 15 | // [x] void cm_destroy_source(cm_Source *self); 16 | // [x] double cm_get_length(cm_Source *self); 17 | // [x] double cm_get_position(cm_Source *self); 18 | // [~] int cm_get_state(cm_Source *self); 19 | // [x] void cm_set_gain(cm_Source *self, double gain); 20 | // [x] void cm_set_pan(cm_Source *self, double pan); 21 | // [x] void cm_set_pitch(cm_Source *self, double pitch); 22 | // [~] void cm_set_loop(cm_Source *self, int loop); 23 | // [x] void cm_play(cm_Source *self); 24 | // [x] void cm_pause(cm_Source *self); 25 | // [x] void cm_stop(cm_Source *self); 26 | 27 | const std = @import("std"); 28 | const Mutex = std.Mutex; 29 | const Allocator = std.mem.Allocator; 30 | 31 | const math = std.math; 32 | pub const Player = @import("../audio.zig").Player; 33 | 34 | const BUFFER_SIZE = 512; 35 | const BUFFER_MASK = BUFFER_SIZE - 1; 36 | 37 | const FX_BITS = 12; 38 | const FX_UNIT = 1 << FX_BITS; 39 | 40 | fn fxFromFloat(f: f32) i32 { 41 | return f * FX_UNIT; 42 | } 43 | 44 | fn fxLerp(comptime T: type, a: T, b: T, p: T) T { 45 | return a + (((b - a) * p) >> FX_BITS); 46 | } 47 | 48 | fn clamp(comptime T: type, x: T, a: T, b: T) T { 49 | const max = math.max(T, a, b); 50 | const min = math.min(T, a, b); 51 | if (x > max) { 52 | return max; 53 | } else if (x < min) { 54 | return min; 55 | } else { 56 | return x; 57 | } 58 | } 59 | 60 | pub const State = enum { 61 | Stopped, 62 | Playing, 63 | Paused, 64 | }; 65 | 66 | pub const EventHandler = fn (Event) void; 67 | 68 | pub const Event = union(enum) { 69 | Lock: void, 70 | Unlock: void, 71 | Rewind: void, 72 | Destroy: void, 73 | Samples: []const i16, 74 | }; 75 | 76 | pub const SourceInfo = struct { 77 | handler: EventHandler, 78 | sample_rate: usize, 79 | length: usize, 80 | }; 81 | 82 | pub const Source = struct { 83 | const Self = @This(); 84 | 85 | mixer: *Mixer, 86 | next: ?*const Source, // next source in list 87 | buffer: [BUFFER_SIZE]i16, // internal buffer with raw stereo pcm 88 | handler: EventHandler, // event handler 89 | sample_rate: usize, // stream's native samplerate 90 | length: usize, // stream's length in frames 91 | end: usize, // end index for the current play-through 92 | pub state: State, // current state 93 | position: i64, // current playhead position (fixed point) 94 | lgain, 95 | rgain: i32, // left and right gain (fixed point) 96 | rate: usize, // playback rate (fixed point) 97 | next_fill: usize, // next frame idx where the buffer needs to be filled 98 | pub loop: bool, // whether the source will loop when `end` is reached 99 | rewind: bool, // whether the source will rewind before playing 100 | active: bool, // whether the source is part of `sources` list 101 | gain: f32, // gain set by `setGain()` 102 | pan: f32, // pan set by `setPan()` 103 | 104 | fn new(mixer: *Mixer, info: SourceInfo) !*Self { 105 | const result = try mixer.allocator.createOne(Self); 106 | result.*.handler = info.handler; 107 | result.*.length = info.length; 108 | result.*.sample_rate = info.sample_rate; 109 | result.setGain(1.0); 110 | result.setPan(0.0); 111 | result.setPitch(1.0); 112 | result.setLoop(false); 113 | result.stop(); 114 | return result; 115 | } 116 | 117 | fn rewindSource(self: *Self) void { 118 | self.handler(Event{ 119 | .Rewind = {}, 120 | }); 121 | self.position = 0; 122 | self.rewind = false; 123 | self.end = self.length; 124 | self.next_fill = 0; 125 | } 126 | 127 | fn fillBuffer(self: *Self, offset: usize, length: usize) void { 128 | const start = offset; 129 | const end = start + length; 130 | self.handler(Event{ 131 | .Samples = self.buffer[start..end], 132 | }); 133 | } 134 | 135 | fn process(self: *Self, length: usize) void { 136 | const dst = self.mixer.buffer; 137 | 138 | // do rewind if flag is set 139 | if (self.rewind) { 140 | self.rewindSource(); 141 | } 142 | 143 | // don't process if not playing 144 | if (self.state == State.Paused) { 145 | return; 146 | } 147 | 148 | // process audio 149 | while (length > 0) { 150 | // get current position frame 151 | const frame = self.position >> FX_BITS; 152 | 153 | // fill buffer if required 154 | if (frame + 3 >= self.next_fill) { 155 | self.fillBuffer((self.next_fill * 2) & BUFFER_MASK, BUFFER_SIZE / 2); 156 | self.next_fill = BUFFER_SIZE / 4; 157 | } 158 | 159 | // handle reaching the end of the playthrough 160 | if (frame >= self.end) { 161 | // ss streams continiously fill the raw buffer in a loop we simply 162 | // increment the end idx by one length and continue reading from it for 163 | // another play-through 164 | self.end = frame + self.length; 165 | // set state and stop processing if we're not set to loop 166 | if (self.loop) { 167 | self.state = State.Stopped; 168 | break; 169 | } 170 | } 171 | 172 | // work out how many frames we should process in the loop 173 | var n = math.min(usize, self.next_fill - 2, self.end) - frame; 174 | const count = blk: { 175 | var c = (n << FX_BITS) / self.rate; 176 | c = math.max(usize, c, 1); 177 | c = math.min(usize, c, length / 2); 178 | break :blk c; 179 | }; 180 | 181 | length -= count * 2; 182 | 183 | // add audio to master buffer 184 | if (self.rate == FX_UNIT) { 185 | // add audio to buffer -- basic 186 | var n = frame * 2; 187 | var i: usize = 0; 188 | while (i < count) : (i += 1) { 189 | dst[(i * 2) + 0] += (self.buffer[(n) & BUFFER_MASK] * self.lgain) >> FX_BITS; 190 | dst[(i * 2) + 1] += (self.buffer[(n + 1) & BUFFER_MASK] * self.rgain) >> FX_BITS; 191 | n += 2; 192 | } 193 | self.position += count * FX_UNIT; 194 | } else { 195 | // add audio to buffer -- interpolated 196 | var i: usize = 0; 197 | while (i < count) : (i += 1) { 198 | const p = self.position & FX_MASK; 199 | var n = (self.position >> FX_BITS) * 2; 200 | var a = self.buffer[(n) & BUFFER_MASK]; 201 | var b = self.buffer[(n + 2) & BUFFER_MASK]; 202 | dst[(i * 2) + 0] += (FX_LERP(a, b, p) * self.lgain) >> FX_BITS; 203 | n += 1; 204 | a = self.buffer[(n) & BUFFER_MASK]; 205 | b = self.buffer[(n + 2) & BUFFER_MASK]; 206 | dst[(i * 2) + 1] += (FX_LERP(a, b, p) * self.rgain) >> FX_BITS; 207 | self.position += self.rate; 208 | } 209 | } 210 | } 211 | } 212 | 213 | pub fn destroy(self: *Self) void { 214 | const held = self.mixer.lock.acquire(); 215 | if (self.active) { 216 | var source = self.mixer.sources; 217 | while (source) |*src| { 218 | if (src.* == self) { 219 | src.* = self.next; 220 | break; 221 | } 222 | } 223 | } 224 | held.release(); 225 | self.handler(Event{ .Destroy = {} }); 226 | self.mixer.allocator.free(self); 227 | self = undefined; 228 | } 229 | 230 | pub fn getLength(self: *const Self) f32 { 231 | return self.length / @intToFloat(f32, self.sample_rate); 232 | } 233 | 234 | pub fn getPosition(self: *const Self) f32 { 235 | return ((self.position >> FX_BITS) % self.length) / @intToFloat(f32, self.sample_rate); 236 | } 237 | 238 | fn recalcGains(self: *Self) void { 239 | self.lgain = fxFromFloat(self.gain * (if (self.pan <= 0.0) 1.0 else 1.0 - pan)); 240 | self.rgain = fxFromFloat(self.gain * (if (self.pan >= 0.0) 1.0 else 1.0 + pan)); 241 | } 242 | 243 | pub fn setGain(self: *Self, gain: f32) void { 244 | self.gain = gain; 245 | self.recalcGains(); 246 | } 247 | 248 | pub fn setPan(self: *Self, pan: f32) void { 249 | self.pan = clamp(f32, -1.0, 1.0); 250 | self.recalcGains(); 251 | } 252 | 253 | pub fn setPitch(self: *Self, pitch: f32) void { 254 | const rate: f32 = if (pitch > 0.0) { 255 | self.samplerate / @intToFloat(f32, self.sample_rate) * pitch; 256 | } else { 257 | 0.001; 258 | }; 259 | self.rate = fxFromFloat(rate); 260 | } 261 | 262 | pub fn play(self: *Self) void { 263 | const held = self.mixer.lock.acquire(); 264 | defer held.release(); 265 | 266 | self.state = State.Playing; 267 | if (!self.active) { 268 | self.active = true; 269 | self.next = self.mixer.sources; 270 | self.mixer.sources = self; 271 | } 272 | } 273 | 274 | pub fn pause(self: *Self) void { 275 | self.state = State.Paused; 276 | } 277 | 278 | pub fn stop(self: *Self) void { 279 | self.state = State.Stopped; 280 | self.rewind = true; 281 | } 282 | }; 283 | 284 | pub const Mixer = struct { 285 | const Self = @This(); 286 | 287 | lock: Mutex, 288 | allocator: *Allocator, 289 | sources: ?*Source, // linked list of active sources 290 | buffer: [BUFFER_SIZE]i32, // internal master buffer 291 | sample_rate: i32, // master samplerate 292 | gain: i32, // master gain (fixed point) 293 | 294 | pub fn init(allocator: *Allocator, sample_rate: i32) Self { 295 | return Self{ 296 | .allocator = allocator, 297 | .sample_rate = sample_rate, 298 | .lock = Mutex.init(), 299 | .sources = null, 300 | .gain = FX_UNIT, 301 | }; 302 | } 303 | 304 | pub fn setGain(self: *Self, gain: f32) void { 305 | self.gain = fxFromFloat(gain); 306 | } 307 | 308 | pub fn process(self: *Self, dst: []const i16) void { 309 | // process in chunks of BUFFER_SIZE if `dst.len` is larger than BUFFER_SIZE 310 | while (dst.len > BUFFER_SIZE) { 311 | self.process(dst[0..BUFFER_SIZE]); 312 | dst = dst[BUFFER_SIZE..]; 313 | } 314 | 315 | // zeroset internal buffer 316 | std.mem.secureZero(i32, self.buffer); 317 | const held = self.lock.acquire(); 318 | 319 | var source = self.src; 320 | while (source) |*src| { 321 | src.process(dst.len); 322 | // remove source from list if no longer plating 323 | if (src.*.state != State.Playing) { 324 | src.*.active = false; 325 | source.?.* = src.next; 326 | } else { 327 | source = src.next; 328 | } 329 | } 330 | 331 | held.release(); 332 | 333 | // copy internal buffer to destination and clip 334 | for (dst) |*d, i| { 335 | const x = (self.buffer[i] * self.gain) >> FX_BITS; 336 | d.* = clamp(i32, -32768, 32767); 337 | } 338 | } 339 | }; 340 | -------------------------------------------------------------------------------- /src/gfx.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | pub const RenderBackend = enum { 8 | OpenGL, 9 | DirectX11, 10 | Metal, 11 | }; 12 | -------------------------------------------------------------------------------- /src/gfx/backend.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | const builtin = @import("builtin"); 8 | const Os = builtin.Os; 9 | 10 | pub const dx11 = switch (builtin.os) { 11 | .windows => @import("backend/dx11.zig"), 12 | else => void, 13 | }; 14 | 15 | pub const opengl = switch (builtin.os) { 16 | .windows, .linux, .macosx => @import("backend/opengl.zig"), 17 | else => void, 18 | }; 19 | 20 | pub const metal = switch (builtin.os) { 21 | .macosx => @import("backend/metal.zig"), 22 | else => void, 23 | }; 24 | -------------------------------------------------------------------------------- /src/gfx/backend/dx11.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | -------------------------------------------------------------------------------- /src/gfx/backend/metal.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | -------------------------------------------------------------------------------- /src/gfx/backend/opengl.zig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emekoi/ziglet/79d65441768e4c473ade7e34527e01f844b9eabe/src/gfx/backend/opengl.zig -------------------------------------------------------------------------------- /src/gfx/spritebatch.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | // ~~stolen from~~ based on [this](https://github.com/RandyGaul/cute_headers/blob/master/cute_spritebatch.h). 8 | 9 | const slotmap = @import("../lib.zig").slotmap; 10 | 11 | pub const Config = struct {}; 12 | pub const Sprite = struct {}; 13 | 14 | pub const SubmitBatch = fn (sprites: []Sprite) void; 15 | 16 | pub const SpriteDescriptor = struct { 17 | width: i32, 18 | height: i32, 19 | x: f32, 20 | y: f32, 21 | sx: f32, 22 | sy: f32, 23 | c: f32, 24 | s: f32, 25 | sort_bits: i32, 26 | }; 27 | 28 | pub const Batch = struct { 29 | pub fn init(config: Config) !Batch { 30 | return Batch{}; 31 | } 32 | 33 | pub fn deinit(self: *Self) void {} 34 | 35 | pub fn push(self: *Batch, image_id: u64, desc: SpriteDescriptor) !void { 36 | return error.NotImplemented; 37 | } 38 | 39 | pub fn tick(self: *Self) void {} 40 | 41 | pub fn flush(self: *Batch) !void { 42 | return error.NotImplemented; 43 | } 44 | 45 | pub fn defrag(self: *Batch) !void { 46 | return error.NotImplemented; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/lib.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | pub const slotmap = @import("lib/slotmap.zig"); 8 | -------------------------------------------------------------------------------- /src/lib/slotmap.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Key = struct { 4 | const Self = @This(); 5 | 6 | index: u32, 7 | version: u32, 8 | 9 | pub fn equals(lhs: Self, rhs: Self) bool { 10 | return lhs.index == rhs.index and lhs.version == rhs.version; 11 | } 12 | }; 13 | 14 | fn Slot(comptime T: type) type { 15 | return struct { 16 | const Self = @This(); 17 | 18 | version: u32, 19 | next_free: u32, 20 | value: T, 21 | 22 | fn new(version: u32, next_free: u32, value: T) Self { 23 | return Self{ 24 | .version = version, 25 | .next_free = next_free, 26 | .value = value, 27 | }; 28 | } 29 | 30 | fn occupied(self: Self) bool { 31 | return self.version % 2 > 0; 32 | } 33 | }; 34 | } 35 | 36 | pub fn SlotMap(comptime T: type) type { 37 | return struct { 38 | const Self = @This(); 39 | const SlotType = Slot(T); 40 | 41 | pub const Error = error{ 42 | OverflowError, 43 | InvalidKey, 44 | }; 45 | 46 | pub const Iterator = struct { 47 | map: *const Self, 48 | index: usize, 49 | 50 | pub fn next_key(self: *Iterator) ?Key { 51 | if (self.map.len == 0 or self.index > self.map.len) { 52 | self.reset(); 53 | return null; 54 | } 55 | while (!self.map.slots.at(self.index).occupied()) : (self.index += 1) {} 56 | self.index += 1; 57 | 58 | return Key{ 59 | .index = @intCast(u32, self.index - 1), 60 | .version = self.map.slots.at(self.index - 1).version, 61 | }; 62 | } 63 | 64 | pub fn next_value(self: *Iterator) ?T { 65 | if (self.map.len == 0 or self.index > self.map.len) { 66 | self.reset(); 67 | return null; 68 | } 69 | while (!self.map.slots.at(self.index).occupied()) : (self.index += 1) {} 70 | self.index += 1; 71 | return self.map.slots.at(self.index - 1).value; 72 | } 73 | 74 | fn reset(self: *Iterator) void { 75 | self.index = 0; 76 | } 77 | }; 78 | 79 | slots: std.ArrayList(SlotType), 80 | free_head: usize, 81 | len: usize, 82 | 83 | pub fn init(allocator: *std.mem.Allocator, size: u32) !Self { 84 | var result = Self{ 85 | .slots = std.ArrayList(SlotType).init(allocator), 86 | .free_head = 0, 87 | .len = 0, 88 | }; 89 | 90 | try result.set_capacity(size); 91 | return result; 92 | } 93 | 94 | pub fn deinit(self: Self) void { 95 | self.slots.deinit(); 96 | } 97 | 98 | pub fn count(self: Self) usize { 99 | return self.len; 100 | } 101 | 102 | pub fn capacity(self: Self) usize { 103 | return self.slots.capacity(); 104 | } 105 | 106 | pub fn set_capacity(self: *Self, new_capacity: usize) !void { 107 | try self.slots.ensureCapacity(new_capacity); 108 | } 109 | 110 | pub fn has_key(self: Self, key: Key) bool { 111 | if (key.index < self.slots.count()) { 112 | const slot = self.slots.at(key.index); 113 | return slot.version == key.version; 114 | } else { 115 | return false; 116 | } 117 | } 118 | 119 | pub fn insert(self: *Self, value: T) !Key { 120 | const new_len = self.len + 1; 121 | 122 | if (new_len == std.math.maxInt(u32)) { 123 | return error.OverflowError; 124 | } 125 | 126 | const idx = self.free_head; 127 | 128 | if (idx < self.slots.count()) { 129 | const slots = self.slots.toSlice(); 130 | const occupied_version = slots[idx].version | 1; 131 | const result = Key{ 132 | .index = @intCast(u32, idx), 133 | .version = occupied_version, 134 | }; 135 | 136 | slots[idx].value = value; 137 | slots[idx].version = occupied_version; 138 | self.free_head = slots[idx].next_free; 139 | self.len = new_len; 140 | 141 | return result; 142 | } else { 143 | const result = Key{ 144 | .index = @intCast(u32, idx), 145 | .version = 1, 146 | }; 147 | 148 | try self.slots.append(SlotType.new(1, 0, value)); 149 | self.free_head = self.slots.count(); 150 | self.len = new_len; 151 | 152 | return result; 153 | } 154 | } 155 | 156 | // TODO: find out how to do this correctly 157 | fn reserve(self: *Self) !Key { 158 | const default: T = undefined; 159 | return try self.insert(default); 160 | } 161 | 162 | fn remove_from_slot(self: *Self, idx: usize) T { 163 | const slots = self.slots.toSlice(); 164 | slots[idx].next_free = @intCast(u32, self.free_head); 165 | slots[idx].version += 1; 166 | self.free_head = idx; 167 | self.len -= 1; 168 | return slots[idx].value; 169 | } 170 | 171 | pub fn remove(self: *Self, key: Key) !T { 172 | if (self.has_key(key)) { 173 | return self.remove_from_slot(key.index); 174 | } else { 175 | return error.InvalidKey; 176 | } 177 | } 178 | 179 | pub fn delete(self: *Self, key: Key) !void { 180 | if (self.has_key(key)) { 181 | _ = self.remove_from_slot(key.index); 182 | } else { 183 | return error.InvalidKey; 184 | } 185 | } 186 | 187 | // TODO: zig closures 188 | fn retain(self: *Self, filter: fn (key: Key, value: T) bool) void { 189 | const len = self.slots.len; 190 | var idx = 0; 191 | 192 | while (idx < len) : (idx += 1) { 193 | const slot = self.slots.at(idx); 194 | const key = Key{ .index = idx, .version = slot.version }; 195 | if (slot.occupied and !filter(key, value)) { 196 | _ = self.remove_from_slot(idx); 197 | } 198 | } 199 | } 200 | 201 | pub fn clear(self: *Self) void { 202 | while (self.len > 0) { 203 | _ = self.remove_from_slot(self.len); 204 | } 205 | 206 | self.slots.shrink(0); 207 | self.free_head = 0; 208 | } 209 | 210 | pub fn get(self: *const Self, key: Key) !T { 211 | if (self.has_key(key)) { 212 | return self.slots.at(key.index).value; 213 | } else { 214 | return error.InvalidKey; 215 | } 216 | } 217 | 218 | pub fn get_ptr(self: *const Self, key: Key) !*T { 219 | if (self.has_key(key)) { 220 | const slots = self.slots.toSlice(); 221 | return &slots[key.index].value; 222 | } else { 223 | return error.InvalidKey; 224 | } 225 | } 226 | 227 | pub fn set(self: *Self, key: Key, value: T) !void { 228 | if (self.has_key(key)) { 229 | const slots = self.slots.toSlice(); 230 | slots[key.index].value = value; 231 | } else { 232 | return error.InvalidKey; 233 | } 234 | } 235 | 236 | pub fn iterator(self: *const Self) Iterator { 237 | return Iterator{ 238 | .map = self, 239 | .index = 0, 240 | }; 241 | } 242 | }; 243 | } 244 | 245 | test "slotmap" { 246 | const debug = std.debug; 247 | const mem = std.mem; 248 | const assert = debug.assert; 249 | const assertError = debug.assertError; 250 | 251 | const data = [][]const u8{ 252 | "foo", 253 | "bar", 254 | "cat", 255 | "zag", 256 | }; 257 | 258 | var map = try SlotMap([]const u8).init(std.debug.global_allocator, 3); 259 | var keys = []Key{Key{ .index = 0, .version = 0 }} ** 3; 260 | var iter = map.iterator(); 261 | var idx: usize = 0; 262 | 263 | defer map.deinit(); 264 | 265 | for (data[0..3]) |word, i| { 266 | keys[i] = try map.insert(word); 267 | } 268 | 269 | assert(mem.eql(u8, try map.get(keys[0]), data[0])); 270 | assert(mem.eql(u8, try map.get(keys[1]), data[1])); 271 | assert(mem.eql(u8, try map.get(keys[2]), data[2])); 272 | 273 | try map.set(keys[0], data[3]); 274 | assert(mem.eql(u8, try map.get(keys[0]), data[3])); 275 | try map.delete(keys[0]); 276 | 277 | assertError(map.get(keys[0]), error.InvalidKey); 278 | 279 | while (iter.next_value()) |value| : (idx += 1) { 280 | assert(mem.eql(u8, value, data[idx + 1])); 281 | } 282 | 283 | idx = 0; 284 | 285 | while (iter.next_key()) |key| : (idx += 1) { 286 | assert(mem.eql(u8, try map.get(key), data[idx + 1])); 287 | } 288 | 289 | map.clear(); 290 | 291 | std.debug.warn("\n"); 292 | 293 | for (keys) |key| { 294 | assertError(map.get(key), error.InvalidKey); 295 | } 296 | 297 | while (iter.next_value()) |value| { 298 | assert(iter.index == 0); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/ziglet.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 emekoi 2 | // 3 | // This library is free software; you can redistribute it and/or modify it 4 | // under the terms of the MIT license. See LICENSE for details. 5 | // 6 | 7 | pub const app = @import("app.zig"); 8 | pub const gfx = @import("gfx.zig"); 9 | pub const audio = @import("audio.zig"); 10 | -------------------------------------------------------------------------------- /ziglet.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "../zig/build/lib/zig/std" 8 | } 9 | ], 10 | "settings": {} 11 | } 12 | --------------------------------------------------------------------------------