diff --git a/.gitmodules b/.gitmodules index 2cc223ca..0a38ff00 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,13 +9,6 @@ shallow = true branch = "mach" -[submodule "examples/libs/zmath"] - path = examples/libs/zmath - url = https://github.com/PiergiorgioZagaria/zmath -[submodule "examples/libs/zigimg"] - path = examples/libs/zigimg - url = https://github.com/zigimg/zigimg - branch = stage2_compat [submodule "freetype/upstream"] path = libs/freetype/upstream url = https://github.com/hexops/freetype @@ -28,15 +21,4 @@ [submodule "basisu/upstream"] path = libs/basisu/upstream url = https://github.com/hexops/basisu -[submodule "examples/image-blur/assets"] - path = examples/image-blur/assets - url = https://github.com/hexops/mach-example-assets -[submodule "examples/textured-cube/assets"] - path = examples/textured-cube/assets - url = https://github.com/hexops/mach-example-assets -[submodule "examples/gkurve/assets"] - path = examples/gkurve/assets - url = https://github.com/hexops/mach-example-assets -[submodule "examples/cubemap/assets"] - path = examples/cubemap/assets - url = https://github.com/hexops/mach-example-assets + diff --git a/build.zig b/build.zig index 5b9b444c..be117f56 100644 --- a/build.zig +++ b/build.zig @@ -110,84 +110,10 @@ pub fn build(b: *Builder) !void { shared_lib.install(); } - try ensureExamplesDependencySubmodules(b.allocator); - inline for ([_]struct { - name: []const u8, - deps: []const Pkg = &.{}, - std_platform_only: bool = false, - has_assets: bool = false, - }{ - .{ .name = "triangle" }, - .{ .name = "triangle-msaa" }, - .{ .name = "boids" }, - .{ .name = "rotating-cube", .deps = &.{Packages.zmath} }, - .{ .name = "pixel-post-process", .deps = &.{Packages.zmath} }, - .{ .name = "two-cubes", .deps = &.{Packages.zmath} }, - .{ .name = "instanced-cube", .deps = &.{Packages.zmath} }, - .{ .name = "advanced-gen-texture-light", .deps = &.{Packages.zmath} }, - .{ .name = "fractal-cube", .deps = &.{Packages.zmath} }, - .{ .name = "textured-cube", .deps = &.{ Packages.zmath, Packages.zigimg }, .has_assets = true }, - .{ .name = "ecs-app", .deps = &.{} }, - .{ .name = "image-blur", .deps = &.{Packages.zigimg}, .has_assets = true }, - .{ .name = "cubemap", .deps = &.{ Packages.zmath, Packages.zigimg }, .has_assets = true }, - .{ .name = "map-async", .deps = &.{} }, - .{ .name = "sysaudio", .deps = &.{} }, - .{ .name = "gkurve", .deps = &.{ Packages.zmath, Packages.zigimg, freetype.pkg }, .std_platform_only = true, .has_assets = true }, - }) |example| { - // FIXME: this is workaround for a problem that some examples - // (having the std_platform_only=true field) as well as zigimg - // uses IO which is not supported in freestanding environments. - // So break out of this loop as soon as any such examples is found. - // This does means that any example which works on wasm should be - // placed before those who dont. - if (example.std_platform_only) - if (target.getCpuArch() == .wasm32) - break; - - const example_app = try App.init( - b, - .{ - .name = "example-" ++ example.name, - .src = "examples/" ++ example.name ++ "/main.zig", - .target = target, - .deps = example.deps, - .res_dirs = if (example.has_assets) &.{"examples/" ++ example.name ++ "/assets"} else null, - .watch_paths = &.{"examples/" ++ example.name}, - }, - ); - example_app.setBuildMode(mode); - inline for (example.deps) |p| { - if (std.mem.eql(u8, p.name, freetype.pkg.name)) - freetype.link(example_app.b, example_app.step, .{}); - } - try example_app.link(options); - example_app.install(); - - const example_compile_step = b.step("example-" ++ example.name, "Compile '" ++ example.name ++ "' example"); - example_compile_step.dependOn(&example_app.getInstallStep().?.step); - - const example_run_cmd = try example_app.run(); - example_run_cmd.dependOn(example_compile_step); - const example_run_step = b.step("run-example-" ++ example.name, "Run '" ++ example.name ++ "' example"); - example_run_step.dependOn(example_run_cmd); - } - - const compile_all = b.step("compile-all", "Compile all examples and applications"); + const compile_all = b.step("compile-all", "Compile Mach"); compile_all.dependOn(b.getInstallStep()); } -const Packages = struct { - // Declared here because submodule may not be cloned at the time build.zig runs. - const zmath = Pkg{ - .name = "zmath", - .source = .{ .path = "examples/libs/zmath/src/zmath.zig" }, - }; - const zigimg = Pkg{ - .name = "zigimg", - .source = .{ .path = "examples/libs/zigimg/zigimg.zig" }, - }; -}; - fn testStep(b: *Builder, mode: std.builtin.Mode, target: CrossTarget) *std.build.RunStep { const main_tests = b.addTestExe("mach-tests", "src/main.zig"); main_tests.setBuildMode(mode); @@ -400,48 +326,6 @@ pub const App = struct { } }; -fn ensureExamplesDependencySubmodules(allocator: std.mem.Allocator) !void { - // TODO(build-system): https://github.com/hexops/mach/issues/229#issuecomment-1100958939 - ensureGit(allocator); - try ensureDependencySubmodule(allocator, "examples/libs/zmath"); - try ensureDependencySubmodule(allocator, "examples/libs/zigimg"); - try ensureDependencySubmodule(allocator, "examples/gkurve/assets"); - try ensureDependencySubmodule(allocator, "examples/image-blur/assets"); - try ensureDependencySubmodule(allocator, "examples/textured-cube/assets"); - try ensureDependencySubmodule(allocator, "examples/cubemap/assets"); -} - -fn ensureDependencySubmodule(allocator: std.mem.Allocator, path: []const u8) !void { - if (std.process.getEnvVarOwned(allocator, "NO_ENSURE_SUBMODULES")) |no_ensure_submodules| { - defer allocator.free(no_ensure_submodules); - if (std.mem.eql(u8, no_ensure_submodules, "true")) return; - } else |_| {} - var child = std.ChildProcess.init(&.{ "git", "submodule", "update", "--init", path }, allocator); - child.cwd = sdkPath("/"); - child.stderr = std.io.getStdErr(); - child.stdout = std.io.getStdOut(); - - _ = try child.spawnAndWait(); -} - -fn ensureGit(allocator: std.mem.Allocator) void { - const result = std.ChildProcess.exec(.{ - .allocator = allocator, - .argv = &.{ "git", "--version" }, - }) catch { // e.g. FileNotFound - std.log.err("mach: error: 'git --version' failed. Is git not installed?", .{}); - std.process.exit(1); - }; - defer { - allocator.free(result.stderr); - allocator.free(result.stdout); - } - if (result.term.Exited != 0) { - std.log.err("mach: error: 'git --version' failed. Is git not installed?", .{}); - std.process.exit(1); - } -} - fn sdkPath(comptime suffix: []const u8) []const u8 { if (suffix[0] != '/') @compileError("suffix must be an absolute path"); return comptime blk: { diff --git a/examples/LICENSE b/examples/LICENSE deleted file mode 100644 index e30d676f..00000000 --- a/examples/LICENSE +++ /dev/null @@ -1,10 +0,0 @@ -The following examples have been ported from https://github.com/austinEng/webgpu-samples and are licensed under BSD 3-Clause License (provided in LICENSE.webgpu-samples): - -* ./boids/ -* ./rotating-cube/ -* ./two-cubes/ -* ./instanced-cube -* ./textured-cube -* ./fractal-cube -* ./image-blur -* ./map-async diff --git a/examples/LICENSE.webgpu-samples b/examples/LICENSE.webgpu-samples deleted file mode 100644 index e7a21bee..00000000 --- a/examples/LICENSE.webgpu-samples +++ /dev/null @@ -1,26 +0,0 @@ -Copyright 2019 WebGPU Samples Contributors - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/advanced-gen-texture-light/cube.wgsl b/examples/advanced-gen-texture-light/cube.wgsl deleted file mode 100644 index 4081f2dc..00000000 --- a/examples/advanced-gen-texture-light/cube.wgsl +++ /dev/null @@ -1,75 +0,0 @@ -struct CameraUniform { - pos: vec4, - view_proj: mat4x4, -}; - -struct InstanceInput { - @location(3) model_matrix_0: vec4, - @location(4) model_matrix_1: vec4, - @location(5) model_matrix_2: vec4, - @location(6) model_matrix_3: vec4, -}; - -struct VertexInput { - @location(0) position: vec3, - @location(1) normal: vec3, - @location(2) tex_coords: vec2, -}; - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) normal: vec3, - @location(2) position: vec3, -}; - -struct Light { - position: vec4, - color: vec4, -}; - -@group(0) @binding(0) var camera: CameraUniform; -@group(1) @binding(0) var t_diffuse: texture_2d; -@group(1) @binding(1) var s_diffuse: sampler; -@group(2) @binding(0) var light: Light; - -@vertex -fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput { - let model_matrix = mat4x4( - instance.model_matrix_0, - instance.model_matrix_1, - instance.model_matrix_2, - instance.model_matrix_3, - ); - var out: VertexOutput; - let world_pos = model_matrix * vec4(model.position, 1.0); - out.position = world_pos.xyz; - out.normal = (model_matrix * vec4(model.normal, 0.0)).xyz; - out.clip_position = camera.view_proj * world_pos; - out.tex_coords = model.tex_coords; - return out; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let object_color = textureSample(t_diffuse, s_diffuse, in.tex_coords); - - let ambient = 0.1; - let ambient_color = light.color.rbg * ambient; - - let light_dir = normalize(light.position.xyz - in.position); - let diffuse = max(dot(in.normal, light_dir), 0.0); - let diffuse_color = light.color.rgb * diffuse; - - let view_dir = normalize(camera.pos.xyz - in.position); - let half_dir = normalize(view_dir + light_dir); - let specular = pow(max(dot(in.normal, half_dir), 0.0), 32.0); - let specular_color = light.color.rbg * specular; - - let all = ambient_color + diffuse_color + specular_color; - - let result = all * object_color.rgb; - - return vec4(result, object_color.a); - -} diff --git a/examples/advanced-gen-texture-light/light.wgsl b/examples/advanced-gen-texture-light/light.wgsl deleted file mode 100644 index e110af20..00000000 --- a/examples/advanced-gen-texture-light/light.wgsl +++ /dev/null @@ -1,35 +0,0 @@ -struct CameraUniform { - view_pos: vec4, - view_proj: mat4x4, -}; - -struct VertexInput { - @location(0) position: vec3, - @location(1) normal: vec3, - @location(2) tex_coords: vec2, -}; - -struct VertexOutput { - @builtin(position) clip_position: vec4, -}; - -struct Light { - position: vec4, - color: vec4, -}; - -@group(0) @binding(0) var camera: CameraUniform; -@group(1) @binding(0) var light: Light; - -@vertex -fn vs_main(model: VertexInput) -> VertexOutput { - var out: VertexOutput; - let world_pos = vec4(model.position + light.position.xyz, 1.0); - out.clip_position = camera.view_proj * world_pos; - return out; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return vec4(1.0, 1.0, 1.0, 0.5); -} diff --git a/examples/advanced-gen-texture-light/main.zig b/examples/advanced-gen-texture-light/main.zig deleted file mode 100755 index d41fb26f..00000000 --- a/examples/advanced-gen-texture-light/main.zig +++ /dev/null @@ -1,826 +0,0 @@ -// in this example: -// - comptime generated image data for texture -// - Blinn-Phong lighting -// - several pipelines -// -// quit with escape, q or space -// move camera with arrows or wasd - -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); - -const Vec = zm.Vec; -const Mat = zm.Mat; -const Quat = zm.Quat; - -pub const App = @This(); - -queue: *gpu.Queue, -cube: Cube, -camera: Camera, -light: Light, -depth: ?Texture, -keys: u8 = 0, - -const Dir = struct { - const up: u8 = 0b0001; - const down: u8 = 0b0010; - const left: u8 = 0b0100; - const right: u8 = 0b1000; -}; - -pub fn init(app: *App, core: *mach.Core) !void { - const eye = vec3(5.0, 7.0, 5.0); - const target = vec3(0.0, 0.0, 0.0); - - const size = core.getFramebufferSize(); - const aspect_ratio = @intToFloat(f32, size.width) / @intToFloat(f32, size.height); - - app.queue = core.device.getQueue(); - app.cube = Cube.init(core); - app.light = Light.init(core); - app.depth = null; - app.camera = Camera.init(core.device, eye, target, vec3(0.0, 1.0, 0.0), aspect_ratio, 45.0, 0.1, 100.0); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.depth.?.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| switch (ev.key) { - .q, .escape, .space => core.close(), - .w, .up => { - app.keys |= Dir.up; - }, - .s, .down => { - app.keys |= Dir.down; - }, - .a, .left => { - app.keys |= Dir.left; - }, - .d, .right => { - app.keys |= Dir.right; - }, - else => {}, - }, - .key_release => |ev| switch (ev.key) { - .w, .up => { - app.keys &= ~Dir.up; - }, - .s, .down => { - app.keys &= ~Dir.down; - }, - .a, .left => { - app.keys &= ~Dir.left; - }, - .d, .right => { - app.keys &= ~Dir.right; - }, - else => {}, - }, - else => {}, - } - } - - // move camera - const speed = zm.f32x4s(@floatCast(f32, core.delta_time * 5)); - const fwd = zm.normalize3(app.camera.target - app.camera.eye); - const right = zm.normalize3(zm.cross3(fwd, app.camera.up)); - - if (app.keys & Dir.up != 0) - app.camera.eye += fwd * speed; - - if (app.keys & Dir.down != 0) - app.camera.eye -= fwd * speed; - - if (app.keys & Dir.right != 0) app.camera.eye += right * speed else if (app.keys & Dir.left != 0) app.camera.eye -= right * speed else app.camera.eye += right * (speed * @Vector(4, f32){ 0.5, 0.5, 0.5, 0.5 }); - - app.camera.update(app.queue); - - // move light - const light_speed = @floatCast(f32, core.delta_time * 2.5); - app.light.update(app.queue, light_speed); - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - defer back_buffer_view.release(); - - const encoder = core.device.createCommandEncoder(null); - defer encoder.release(); - - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = gpu.Color{ .r = 0.0, .g = 0.0, .b = 0.4, .a = 1.0 }, - .load_op = .clear, - .store_op = .store, - }; - - const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - .depth_stencil_attachment = &.{ - .view = app.depth.?.view, - .depth_load_op = .clear, - .depth_store_op = .store, - .depth_clear_value = 1.0, - }, - }); - - const pass = encoder.beginRenderPass(&render_pass_descriptor); - defer pass.release(); - - // brick cubes - pass.setPipeline(app.cube.pipeline); - pass.setBindGroup(0, app.camera.bind_group, &.{}); - pass.setBindGroup(1, app.cube.texture.bind_group, &.{}); - pass.setBindGroup(2, app.light.bind_group, &.{}); - pass.setVertexBuffer(0, app.cube.mesh.buffer, 0, app.cube.mesh.size); - pass.setVertexBuffer(1, app.cube.instance.buffer, 0, app.cube.instance.size); - pass.draw(4, app.cube.instance.len, 0, 0); - pass.draw(4, app.cube.instance.len, 4, 0); - pass.draw(4, app.cube.instance.len, 8, 0); - pass.draw(4, app.cube.instance.len, 12, 0); - pass.draw(4, app.cube.instance.len, 16, 0); - pass.draw(4, app.cube.instance.len, 20, 0); - - // light source - pass.setPipeline(app.light.pipeline); - pass.setBindGroup(0, app.camera.bind_group, &.{}); - pass.setBindGroup(1, app.light.bind_group, &.{}); - pass.setVertexBuffer(0, app.cube.mesh.buffer, 0, app.cube.mesh.size); - pass.draw(4, 1, 0, 0); - pass.draw(4, 1, 4, 0); - pass.draw(4, 1, 8, 0); - pass.draw(4, 1, 12, 0); - pass.draw(4, 1, 16, 0); - pass.draw(4, 1, 20, 0); - - pass.end(); - - var command = encoder.finish(null); - defer command.release(); - - app.queue.submit(&.{command}); - core.swap_chain.?.present(); -} - -pub fn resize(app: *App, core: *mach.Core, width: u32, height: u32) !void { - // If window is resized, recreate depth buffer otherwise we cannot use it. - if (app.depth != null) { - app.depth.?.release(); - } - // It also recreates the sampler, which is a waste, but for an example it's ok - app.depth = Texture.depth(core.device, width, height); -} - -const Camera = struct { - const Self = @This(); - - eye: Vec, - target: Vec, - up: Vec, - aspect: f32, - fovy: f32, - near: f32, - far: f32, - bind_group: *gpu.BindGroup, - buffer: Buffer, - - const Uniform = extern struct { - pos: Vec, - mat: Mat, - }; - - fn init(device: *gpu.Device, eye: Vec, target: Vec, up: Vec, aspect: f32, fovy: f32, near: f32, far: f32) Self { - var self: Self = .{ - .eye = eye, - .target = target, - .up = up, - .aspect = aspect, - .near = near, - .far = far, - .fovy = fovy, - .buffer = undefined, - .bind_group = undefined, - }; - - const view = self.buildViewProjMatrix(); - - const uniform = Uniform{ - .pos = self.eye, - .mat = view, - }; - - const buffer = .{ - .buffer = initBuffer(device, .{ .uniform = true }, &@bitCast([20]f32, uniform)), - .size = @sizeOf(@TypeOf(uniform)), - }; - - const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = Self.bindGroupLayout(device), - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size), - }, - })); - - self.buffer = buffer; - self.bind_group = bind_group; - - return self; - } - - fn update(self: *Self, queue: *gpu.Queue) void { - const mat = self.buildViewProjMatrix(); - const uniform = .{ - .pos = self.eye, - .mat = mat, - }; - - queue.writeBuffer(self.buffer.buffer, 0, &[_]Uniform{uniform}); - } - - inline fn buildViewProjMatrix(s: *const Camera) Mat { - const view = zm.lookAtRh(s.eye, s.target, s.up); - const proj = zm.perspectiveFovRh(s.fovy, s.aspect, s.near, s.far); - return zm.mul(view, proj); - } - - inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { - const visibility = .{ .vertex = true, .fragment = true }; - return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ - gpu.BindGroupLayout.Entry.buffer(0, visibility, .uniform, false, 0), - }, - })); - } -}; - -const Buffer = struct { - buffer: *gpu.Buffer, - size: usize, - len: u32 = 0, -}; - -const Cube = struct { - const Self = @This(); - - pipeline: *gpu.RenderPipeline, - mesh: Buffer, - instance: Buffer, - texture: Texture, - - const IPR = 20; // instances per row - const SPACING = 2; // spacing between cubes - const DISPLACEMENT = vec3u(IPR * SPACING / 2, 0, IPR * SPACING / 2); - - fn init(core: *mach.Core) Self { - const device = core.device; - - const texture = Brick.texture(device); - - // instance buffer - var ibuf: [IPR * IPR * 16]f32 = undefined; - - var z: usize = 0; - while (z < IPR) : (z += 1) { - var x: usize = 0; - while (x < IPR) : (x += 1) { - const pos = vec3u(x * SPACING, 0, z * SPACING) - DISPLACEMENT; - const rot = blk: { - if (pos[0] == 0 and pos[2] == 0) { - break :blk zm.quatFromAxisAngle(vec3u(0, 0, 1), 0.0); - } else { - break :blk zm.quatFromAxisAngle(zm.normalize3(pos), 45.0); - } - }; - const index = z * IPR + x; - const inst = Instance{ - .position = pos, - .rotation = rot, - }; - zm.storeMat(ibuf[index * 16 ..], inst.toMat()); - } - } - - const instance = Buffer{ - .buffer = initBuffer(device, .{ .vertex = true }, &ibuf), - .len = IPR * IPR, - .size = @sizeOf(@TypeOf(ibuf)), - }; - - return Self{ - .mesh = mesh(device), - .texture = texture, - .instance = instance, - .pipeline = pipeline(core), - }; - } - - fn pipeline(core: *mach.Core) *gpu.RenderPipeline { - const device = core.device; - - const layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &.{ - Camera.bindGroupLayout(device), - Texture.bindGroupLayout(device), - Light.bindGroupLayout(device), - }, - }); - - const layout = device.createPipelineLayout(&layout_descriptor); - defer layout.release(); - - const shader = device.createShaderModuleWGSL("cube.wgsl", @embedFile("cube.wgsl")); - defer shader.release(); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - }; - - const fragment = gpu.FragmentState.init(.{ - .module = shader, - .entry_point = "fs_main", - .targets = &.{color_target}, - }); - - const descriptor = gpu.RenderPipeline.Descriptor{ - .layout = layout, - .fragment = &fragment, - .vertex = gpu.VertexState.init(.{ - .module = shader, - .entry_point = "vs_main", - .buffers = &.{ - Self.vertexBufferLayout(), - Self.instanceLayout(), - }, - }), - .depth_stencil = &.{ - .format = Texture.DEPTH_FORMAT, - .depth_write_enabled = true, - .depth_compare = .less, - }, - .primitive = .{ - .cull_mode = .back, - .topology = .triangle_strip, - }, - }; - - return device.createRenderPipeline(&descriptor); - } - - fn mesh(device: *gpu.Device) Buffer { - // generated texture has aspect ratio of 1:2 - // `h` reflects that ratio - // `v` sets how many times texture repeats across surface - const v = 2; - const h = v * 2; - const buf = asFloats(.{ - // z+ face - 0, 0, 1, 0, 0, 1, 0, h, - 1, 0, 1, 0, 0, 1, v, h, - 0, 1, 1, 0, 0, 1, 0, 0, - 1, 1, 1, 0, 0, 1, v, 0, - // z- face - 1, 0, 0, 0, 0, -1, 0, h, - 0, 0, 0, 0, 0, -1, v, h, - 1, 1, 0, 0, 0, -1, 0, 0, - 0, 1, 0, 0, 0, -1, v, 0, - // x+ face - 1, 0, 1, 1, 0, 0, 0, h, - 1, 0, 0, 1, 0, 0, v, h, - 1, 1, 1, 1, 0, 0, 0, 0, - 1, 1, 0, 1, 0, 0, v, 0, - // x- face - 0, 0, 0, -1, 0, 0, 0, h, - 0, 0, 1, -1, 0, 0, v, h, - 0, 1, 0, -1, 0, 0, 0, 0, - 0, 1, 1, -1, 0, 0, v, 0, - // y+ face - 1, 1, 0, 0, 1, 0, 0, h, - 0, 1, 0, 0, 1, 0, v, h, - 1, 1, 1, 0, 1, 0, 0, 0, - 0, 1, 1, 0, 1, 0, v, 0, - // y- face - 0, 0, 0, 0, -1, 0, 0, h, - 1, 0, 0, 0, -1, 0, v, h, - 0, 0, 1, 0, -1, 0, 0, 0, - 1, 0, 1, 0, -1, 0, v, 0, - }); - - return Buffer{ - .buffer = initBuffer(device, .{ .vertex = true }, &buf), - .size = @sizeOf(@TypeOf(buf)), - }; - } - - fn vertexBufferLayout() gpu.VertexBufferLayout { - const attributes = [_]gpu.VertexAttribute{ - .{ - .format = .float32x3, - .offset = 0, - .shader_location = 0, - }, - .{ - .format = .float32x3, - .offset = @sizeOf([3]f32), - .shader_location = 1, - }, - .{ - .format = .float32x2, - .offset = @sizeOf([6]f32), - .shader_location = 2, - }, - }; - return gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf([8]f32), - .attributes = &attributes, - }); - } - - fn instanceLayout() gpu.VertexBufferLayout { - const attributes = [_]gpu.VertexAttribute{ - .{ - .format = .float32x4, - .offset = 0, - .shader_location = 3, - }, - .{ - .format = .float32x4, - .offset = @sizeOf([4]f32), - .shader_location = 4, - }, - .{ - .format = .float32x4, - .offset = @sizeOf([8]f32), - .shader_location = 5, - }, - .{ - .format = .float32x4, - .offset = @sizeOf([12]f32), - .shader_location = 6, - }, - }; - - return gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf([16]f32), - .step_mode = .instance, - .attributes = &attributes, - }); - } -}; - -fn asFloats(comptime arr: anytype) [arr.len]f32 { - comptime var len = arr.len; - comptime var out: [len]f32 = undefined; - comptime var i = 0; - inline while (i < len) : (i += 1) { - out[i] = @intToFloat(f32, arr[i]); - } - return out; -} - -const Brick = struct { - const W = 12; - const H = 6; - - fn texture(device: *gpu.Device) Texture { - const slice: []const u8 = &data(); - return Texture.fromData(device, W, H, u8, slice); - } - - fn data() [W * H * 4]u8 { - comptime var out: [W * H * 4]u8 = undefined; - - // fill all the texture with brick color - comptime var i = 0; - inline while (i < H) : (i += 1) { - comptime var j = 0; - inline while (j < W * 4) : (j += 4) { - out[i * W * 4 + j + 0] = 210; - out[i * W * 4 + j + 1] = 30; - out[i * W * 4 + j + 2] = 30; - out[i * W * 4 + j + 3] = 0; - } - } - - const f = 10; - - // fill the cement lines - inline for ([_]comptime_int{ 0, 1 }) |k| { - inline for ([_]comptime_int{ 5 * 4, 11 * 4 }) |m| { - out[k * W * 4 + m + 0] = f; - out[k * W * 4 + m + 1] = f; - out[k * W * 4 + m + 2] = f; - out[k * W * 4 + m + 3] = 0; - } - } - - inline for ([_]comptime_int{ 3, 4 }) |k| { - inline for ([_]comptime_int{ 2 * 4, 8 * 4 }) |m| { - out[k * W * 4 + m + 0] = f; - out[k * W * 4 + m + 1] = f; - out[k * W * 4 + m + 2] = f; - out[k * W * 4 + m + 3] = 0; - } - } - - inline for ([_]comptime_int{ 2, 5 }) |k| { - comptime var m = 0; - inline while (m < W * 4) : (m += 4) { - out[k * W * 4 + m + 0] = f; - out[k * W * 4 + m + 1] = f; - out[k * W * 4 + m + 2] = f; - out[k * W * 4 + m + 3] = 0; - } - } - - return out; - } -}; - -// don't confuse with gpu.Texture -const Texture = struct { - const Self = @This(); - - texture: *gpu.Texture, - view: *gpu.TextureView, - sampler: *gpu.Sampler, - bind_group: *gpu.BindGroup, - - const DEPTH_FORMAT = .depth32_float; - const FORMAT = .rgba8_unorm; - - fn release(self: *Self) void { - self.texture.release(); - self.view.release(); - self.sampler.release(); - } - - fn fromData(device: *gpu.Device, width: u32, height: u32, comptime T: type, data: []const T) Self { - const extent = gpu.Extent3D{ - .width = width, - .height = height, - }; - - const texture = device.createTexture(&gpu.Texture.Descriptor{ - .size = extent, - .format = FORMAT, - .usage = .{ .copy_dst = true, .texture_binding = true }, - }); - - const view = texture.createView(&gpu.TextureView.Descriptor{ - .format = FORMAT, - .dimension = .dimension_2d, - .array_layer_count = 1, - .mip_level_count = 1, - }); - - const sampler = device.createSampler(&gpu.Sampler.Descriptor{ - .address_mode_u = .repeat, - .address_mode_v = .repeat, - .address_mode_w = .repeat, - .mag_filter = .linear, - .min_filter = .linear, - .mipmap_filter = .linear, - .max_anisotropy = 1, // 1,2,4,8,16 - }); - - device.getQueue().writeTexture( - &gpu.ImageCopyTexture{ - .texture = texture, - }, - &gpu.Texture.DataLayout{ - .bytes_per_row = 4 * width, - .rows_per_image = height, - }, - &extent, - data, - ); - - const bind_group_layout = Self.bindGroupLayout(device); - const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = bind_group_layout, - .entries = &.{ - gpu.BindGroup.Entry.textureView(0, view), - gpu.BindGroup.Entry.sampler(1, sampler), - }, - })); - - return Self{ - .view = view, - .texture = texture, - .sampler = sampler, - .bind_group = bind_group, - }; - } - - fn depth(device: *gpu.Device, width: u32, height: u32) Self { - const extent = gpu.Extent3D{ - .width = width, - .height = height, - }; - - const texture = device.createTexture(&gpu.Texture.Descriptor{ - .size = extent, - .format = DEPTH_FORMAT, - .usage = .{ - .render_attachment = true, - .texture_binding = true, - }, - }); - - const view = texture.createView(&gpu.TextureView.Descriptor{ - .dimension = .dimension_2d, - .array_layer_count = 1, - .mip_level_count = 1, - }); - - const sampler = device.createSampler(&gpu.Sampler.Descriptor{ - .mag_filter = .linear, - .compare = .less_equal, - }); - - return Self{ - .texture = texture, - .view = view, - .sampler = sampler, - .bind_group = undefined, // not used - }; - } - - inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { - const visibility = .{ .fragment = true }; - const Entry = gpu.BindGroupLayout.Entry; - return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ - Entry.texture(0, visibility, .float, .dimension_2d, false), - Entry.sampler(1, visibility, .filtering), - }, - })); - } -}; - -const Light = struct { - const Self = @This(); - - uniform: Uniform, - buffer: Buffer, - bind_group: *gpu.BindGroup, - pipeline: *gpu.RenderPipeline, - - const Uniform = extern struct { - position: Vec, - color: Vec, - }; - - fn init(core: *mach.Core) Self { - const device = core.device; - const uniform = Uniform{ - .color = vec3u(1, 1, 1), - .position = vec3u(3, 7, 2), - }; - - const buffer = .{ - .buffer = initBuffer(device, .{ .uniform = true }, &@bitCast([8]f32, uniform)), - .size = @sizeOf(@TypeOf(uniform)), - }; - - const bind_group = device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = Self.bindGroupLayout(device), - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, buffer.buffer, 0, buffer.size), - }, - })); - - return Self{ - .buffer = buffer, - .uniform = uniform, - .bind_group = bind_group, - .pipeline = Self.pipeline(core), - }; - } - - fn update(self: *Self, queue: *gpu.Queue, delta: f32) void { - const old = self.uniform; - const new = Light.Uniform{ - .position = zm.qmul(zm.quatFromAxisAngle(vec3u(0, 1, 0), delta), old.position), - .color = old.color, - }; - queue.writeBuffer(self.buffer.buffer, 0, &[_]Light.Uniform{new}); - self.uniform = new; - } - - inline fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout { - const visibility = .{ .vertex = true, .fragment = true }; - const Entry = gpu.BindGroupLayout.Entry; - return device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ - Entry.buffer(0, visibility, .uniform, false, 0), - }, - })); - } - - fn pipeline(core: *mach.Core) *gpu.RenderPipeline { - const device = core.device; - - const layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &.{ - Camera.bindGroupLayout(device), - Light.bindGroupLayout(device), - }, - }); - - const layout = device.createPipelineLayout(&layout_descriptor); - defer layout.release(); - - const shader = core.device.createShaderModuleWGSL("light.wgsl", @embedFile("light.wgsl")); - defer shader.release(); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - }; - - const fragment = gpu.FragmentState.init(.{ - .module = shader, - .entry_point = "fs_main", - .targets = &.{color_target}, - }); - - const descriptor = gpu.RenderPipeline.Descriptor{ - .layout = layout, - .fragment = &fragment, - .vertex = gpu.VertexState.init(.{ - .module = shader, - .entry_point = "vs_main", - .buffers = &.{ - Cube.vertexBufferLayout(), - }, - }), - .depth_stencil = &.{ - .format = Texture.DEPTH_FORMAT, - .depth_write_enabled = true, - .depth_compare = .less, - }, - .primitive = .{ - .cull_mode = .back, - .topology = .triangle_strip, - }, - }; - - return device.createRenderPipeline(&descriptor); - } -}; - -inline fn initBuffer(device: *gpu.Device, usage: gpu.Buffer.UsageFlags, data: anytype) *gpu.Buffer { - std.debug.assert(@typeInfo(@TypeOf(data)) == .Pointer); - const T = std.meta.Elem(@TypeOf(data)); - - var u = usage; - u.copy_dst = true; - const buffer = device.createBuffer(&.{ - .size = @sizeOf(T) * data.len, - .usage = u, - .mapped_at_creation = true, - }); - - var mapped = buffer.getMappedRange(T, 0, data.len); - std.mem.copy(T, mapped.?, data); - buffer.unmap(); - return buffer; -} - -fn vec3i(x: isize, y: isize, z: isize) Vec { - return zm.f32x4(@intToFloat(f32, x), @intToFloat(f32, y), @intToFloat(f32, z), 0.0); -} - -fn vec3u(x: usize, y: usize, z: usize) Vec { - return zm.f32x4(@intToFloat(f32, x), @intToFloat(f32, y), @intToFloat(f32, z), 0.0); -} - -fn vec3(x: f32, y: f32, z: f32) Vec { - return zm.f32x4(x, y, z, 0.0); -} - -fn vec4(x: f32, y: f32, z: f32, w: f32) Vec { - return zm.f32x4(x, y, z, w); -} - -// todo indside Cube -const Instance = struct { - const Self = @This(); - - position: Vec, - rotation: Quat, - - fn toMat(self: *const Self) Mat { - return zm.mul(zm.quatToMat(self.rotation), zm.translationV(self.position)); - } -}; diff --git a/examples/boids/main.zig b/examples/boids/main.zig deleted file mode 100644 index 11d8fa4a..00000000 --- a/examples/boids/main.zig +++ /dev/null @@ -1,220 +0,0 @@ -/// A port of Austin Eng's "computeBoids" webgpu sample. -/// https://github.com/austinEng/webgpu-samples/blob/main/src/sample/computeBoids/main.ts -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); - -compute_pipeline: *gpu.ComputePipeline, -render_pipeline: *gpu.RenderPipeline, -sprite_vertex_buffer: *gpu.Buffer, -particle_buffers: [2]*gpu.Buffer, -particle_bind_groups: [2]*gpu.BindGroup, -sim_param_buffer: *gpu.Buffer, -frame_counter: usize, - -pub const App = @This(); - -const num_particle = 1500; - -var sim_params = [_]f32{ - 0.04, // .delta_T - 0.1, // .rule_1_distance - 0.025, // .rule_2_distance - 0.025, // .rule_3_distance - 0.02, // .rule_1_scale - 0.05, // .rule_2_scale - 0.005, // .rule_3_scale -}; - -pub fn init(app: *App, core: *mach.Core) !void { - const sprite_shader_module = core.device.createShaderModuleWGSL( - "sprite.wgsl", - @embedFile("sprite.wgsl"), - ); - - const update_sprite_shader_module = core.device.createShaderModuleWGSL( - "updateSprites.wgsl", - @embedFile("updateSprites.wgsl"), - ); - - const instanced_particles_attributes = [_]gpu.VertexAttribute{ - .{ - // instance position - .shader_location = 0, - .offset = 0, - .format = .float32x2, - }, - .{ - // instance velocity - .shader_location = 1, - .offset = 2 * 4, - .format = .float32x2, - }, - }; - - const vertex_buffer_attributes = [_]gpu.VertexAttribute{ - .{ - // vertex positions - .shader_location = 2, - .offset = 0, - .format = .float32x2, - }, - }; - - const render_pipeline = core.device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{ - .vertex = gpu.VertexState.init(.{ - .module = sprite_shader_module, - .entry_point = "vert_main", - .buffers = &.{ - gpu.VertexBufferLayout.init(.{ - // instanced particles buffer - .array_stride = 4 * 4, - .step_mode = .instance, - .attributes = &instanced_particles_attributes, - }), - gpu.VertexBufferLayout.init(.{ - // vertex buffer - .array_stride = 2 * 4, - .step_mode = .vertex, - .attributes = &vertex_buffer_attributes, - }), - }, - }), - .fragment = &gpu.FragmentState.init(.{ - .module = sprite_shader_module, - .entry_point = "frag_main", - .targets = &[_]gpu.ColorTargetState{.{ - .format = core.swap_chain_format, - }}, - }), - }); - - const compute_pipeline = core.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{ - .module = update_sprite_shader_module, - .entry_point = "main", - } }); - - const vert_buffer_data = [_]f32{ - -0.01, -0.02, 0.01, - -0.02, 0.0, 0.02, - }; - - const sprite_vertex_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{ - .label = "sprite_vertex_buffer", - .usage = .{ .vertex = true }, - .mapped_at_creation = true, - .size = vert_buffer_data.len * @sizeOf(f32), - }); - var vertex_mapped = sprite_vertex_buffer.getMappedRange(f32, 0, vert_buffer_data.len); - std.mem.copy(f32, vertex_mapped.?, vert_buffer_data[0..]); - sprite_vertex_buffer.unmap(); - - const sim_param_buffer = core.device.createBuffer(&gpu.Buffer.Descriptor{ - .label = "sim_param_buffer", - .usage = .{ .uniform = true, .copy_dst = true }, - .size = sim_params.len * @sizeOf(f32), - }); - core.device.getQueue().writeBuffer(sim_param_buffer, 0, sim_params[0..]); - - var initial_particle_data: [num_particle * 4]f32 = undefined; - var rng = std.rand.DefaultPrng.init(0); - const random = rng.random(); - var i: usize = 0; - while (i < num_particle) : (i += 1) { - initial_particle_data[4 * i + 0] = 2 * (random.float(f32) - 0.5); - initial_particle_data[4 * i + 1] = 2 * (random.float(f32) - 0.5); - initial_particle_data[4 * i + 2] = 2 * (random.float(f32) - 0.5) * 0.1; - initial_particle_data[4 * i + 3] = 2 * (random.float(f32) - 0.5) * 0.1; - } - - var particle_buffers: [2]*gpu.Buffer = undefined; - var particle_bind_groups: [2]*gpu.BindGroup = undefined; - i = 0; - while (i < 2) : (i += 1) { - particle_buffers[i] = core.device.createBuffer(&gpu.Buffer.Descriptor{ - .label = "particle_buffer", - .mapped_at_creation = true, - .usage = .{ - .vertex = true, - .storage = true, - }, - .size = initial_particle_data.len * @sizeOf(f32), - }); - var mapped = particle_buffers[i].getMappedRange(f32, 0, initial_particle_data.len); - std.mem.copy(f32, mapped.?, initial_particle_data[0..]); - particle_buffers[i].unmap(); - } - - i = 0; - while (i < 2) : (i += 1) { - particle_bind_groups[i] = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = compute_pipeline.getBindGroupLayout(0), - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, sim_param_buffer, 0, sim_params.len * @sizeOf(f32)), - gpu.BindGroup.Entry.buffer(1, particle_buffers[i], 0, initial_particle_data.len * @sizeOf(f32)), - gpu.BindGroup.Entry.buffer(2, particle_buffers[(i + 1) % 2], 0, initial_particle_data.len * @sizeOf(f32)), - }, - })); - } - - app.compute_pipeline = compute_pipeline; - app.render_pipeline = render_pipeline; - app.sprite_vertex_buffer = sprite_vertex_buffer; - app.particle_buffers = particle_buffers; - app.particle_bind_groups = particle_bind_groups; - app.sim_param_buffer = sim_param_buffer; - app.frame_counter = 0; -} - -pub fn deinit(_: *App, _: *mach.Core) void {} - -pub fn update(app: *App, core: *mach.Core) !void { - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{ - color_attachment, - }, - }); - - sim_params[0] = @floatCast(f32, core.delta_time); - core.device.getQueue().writeBuffer(app.sim_param_buffer, 0, sim_params[0..]); - - const command_encoder = core.device.createCommandEncoder(null); - { - const pass_encoder = command_encoder.beginComputePass(null); - pass_encoder.setPipeline(app.compute_pipeline); - pass_encoder.setBindGroup(0, app.particle_bind_groups[app.frame_counter % 2], null); - pass_encoder.dispatchWorkgroups(@floatToInt(u32, @ceil(@as(f32, num_particle) / 64)), 1, 1); - pass_encoder.end(); - pass_encoder.release(); - } - { - const pass_encoder = command_encoder.beginRenderPass(&render_pass_descriptor); - pass_encoder.setPipeline(app.render_pipeline); - pass_encoder.setVertexBuffer(0, app.particle_buffers[(app.frame_counter + 1) % 2], 0, num_particle * 4 * @sizeOf(f32)); - pass_encoder.setVertexBuffer(1, app.sprite_vertex_buffer, 0, 6 * @sizeOf(f32)); - pass_encoder.draw(3, num_particle, 0, 0); - pass_encoder.end(); - pass_encoder.release(); - } - - app.frame_counter += 1; - if (app.frame_counter % 60 == 0) { - std.log.info("Frame {}", .{app.frame_counter}); - } - - var command = command_encoder.finish(null); - command_encoder.release(); - core.device.getQueue().submit(&.{command}); - command.release(); - - core.swap_chain.?.present(); - back_buffer_view.release(); -} diff --git a/examples/boids/sprite.wgsl b/examples/boids/sprite.wgsl deleted file mode 100644 index c97c5c18..00000000 --- a/examples/boids/sprite.wgsl +++ /dev/null @@ -1,15 +0,0 @@ -@vertex -fn vert_main(@location(0) a_particlePos : vec2, - @location(1) a_particleVel : vec2, - @location(2) a_pos : vec2) -> @builtin(position) vec4 { - let angle = -atan2(a_particleVel.x, a_particleVel.y); - let pos = vec2( - (a_pos.x * cos(angle)) - (a_pos.y * sin(angle)), - (a_pos.x * sin(angle)) + (a_pos.y * cos(angle))); - return vec4(pos + a_particlePos, 0.0, 1.0); -} - -@fragment -fn frag_main() -> @location(0) vec4 { - return vec4(1.0, 1.0, 1.0, 1.0); -} diff --git a/examples/boids/updateSprites.wgsl b/examples/boids/updateSprites.wgsl deleted file mode 100644 index 730102b6..00000000 --- a/examples/boids/updateSprites.wgsl +++ /dev/null @@ -1,86 +0,0 @@ -struct Particle { - pos : vec2, - vel : vec2, -}; -struct SimParams { - deltaT : f32, - rule1Distance : f32, - rule2Distance : f32, - rule3Distance : f32, - rule1Scale : f32, - rule2Scale : f32, - rule3Scale : f32, -}; -struct Particles { - particles : array, -}; -@binding(0) @group(0) var params : SimParams; -@binding(1) @group(0) var particlesA : Particles; -@binding(2) @group(0) var particlesB : Particles; - -// https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp -@compute @workgroup_size(64) -fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3) { - var index : u32 = GlobalInvocationID.x; - - var vPos = particlesA.particles[index].pos; - var vVel = particlesA.particles[index].vel; - var cMass = vec2(0.0, 0.0); - var cVel = vec2(0.0, 0.0); - var colVel = vec2(0.0, 0.0); - var cMassCount : u32 = 0u; - var cVelCount : u32 = 0u; - var pos : vec2; - var vel : vec2; - - for (var i : u32 = 0u; i < arrayLength(&particlesA.particles); i = i + 1u) { - if (i == index) { - continue; - } - - pos = particlesA.particles[i].pos.xy; - vel = particlesA.particles[i].vel.xy; - if (distance(pos, vPos) < params.rule1Distance) { - cMass = cMass + pos; - cMassCount = cMassCount + 1u; - } - if (distance(pos, vPos) < params.rule2Distance) { - colVel = colVel - (pos - vPos); - } - if (distance(pos, vPos) < params.rule3Distance) { - cVel = cVel + vel; - cVelCount = cVelCount + 1u; - } - } - if (cMassCount > 0u) { - var temp = f32(cMassCount); - cMass = (cMass / vec2(temp, temp)) - vPos; - } - if (cVelCount > 0u) { - var temp = f32(cVelCount); - cVel = cVel / vec2(temp, temp); - } - vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) + - (cVel * params.rule3Scale); - - // clamp velocity for a more pleasing simulation - vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); - // kinematic update - vPos = vPos + (vVel * params.deltaT); - // Wrap around boundary - if (vPos.x < -1.0) { - vPos.x = 1.0; - } - if (vPos.x > 1.0) { - vPos.x = -1.0; - } - if (vPos.y < -1.0) { - vPos.y = 1.0; - } - if (vPos.y > 1.0) { - vPos.y = -1.0; - } - // Write back - particlesB.particles[index].pos = vPos; - particlesB.particles[index].vel = vVel; -} diff --git a/examples/cubemap/assets b/examples/cubemap/assets deleted file mode 160000 index a106279b..00000000 --- a/examples/cubemap/assets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a106279bd4a5c4a7b6860cac6a0a0c0a6c727401 diff --git a/examples/cubemap/cube_mesh.zig b/examples/cubemap/cube_mesh.zig deleted file mode 100644 index ae5b2912..00000000 --- a/examples/cubemap/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(4, f32), - col: @Vector(4, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, -}; diff --git a/examples/cubemap/frag.wgsl b/examples/cubemap/frag.wgsl deleted file mode 100644 index 8b6b2591..00000000 --- a/examples/cubemap/frag.wgsl +++ /dev/null @@ -1,11 +0,0 @@ -@group(0) @binding(1) var mySampler: sampler; -@group(0) @binding(2) var myTexture: texture_cube; - -@fragment -fn main( - @location(0) fragUV: vec2, - @location(1) fragPosition: vec4 -) -> @location(0) vec4 { - var cubemapVec = fragPosition.xyz - vec3(0.5, 0.5, 0.5); - return textureSample(myTexture, mySampler, cubemapVec); -} diff --git a/examples/cubemap/main.zig b/examples/cubemap/main.zig deleted file mode 100644 index 6cc7cdec..00000000 --- a/examples/cubemap/main.zig +++ /dev/null @@ -1,352 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); -const zigimg = @import("zigimg"); -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; - -const UniformBufferObject = struct { - mat: zm.Mat, -}; - -var timer: mach.Timer = undefined; - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, -depth_texture: ?*gpu.Texture, -depth_texture_view: *gpu.TextureView, - -pub const App = @This(); - -pub fn init(app: *App, core: *mach.Core) !void { - timer = try mach.Timer.start(); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, - }; - - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const blend = gpu.BlendState{ - .color = .{ - .operation = .add, - .src_factor = .src_alpha, - .dst_factor = .one_minus_src_alpha, - }, - .alpha = .{ - .operation = .add, - .src_factor = .one, - .dst_factor = .zero, - }, - }; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - // Enable depth testing so that the fragment closest to the camera - // is rendered in front. - .depth_stencil = &.{ - .format = .depth24_plus, - .depth_write_enabled = true, - .depth_compare = .less, - }, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }), - .primitive = .{ - // Since the cube has its face pointing outwards, cull_mode must be - // set to .front or .none here since we are inside the cube looking out. - // Ideally you would set this to .back and have a custom cube primitive - // with the faces pointing towards the inside of the cube. - .cull_mode = .none, - }, - }; - const pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject), - .mapped_at_creation = false, - }); - - // Create a sampler with linear filtering for smooth interpolation. - const sampler = core.device.createSampler(&.{ - .mag_filter = .linear, - .min_filter = .linear, - }); - - const queue = core.device.getQueue(); - - // WebGPU expects the cubemap textures in this order: (+X,-X,+Y,-Y,+Z,-Z) - var images: [6]zigimg.Image = undefined; - images[0] = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/skybox/posx.png")); - defer images[0].deinit(); - images[1] = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/skybox/negx.png")); - defer images[1].deinit(); - images[2] = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/skybox/posy.png")); - defer images[2].deinit(); - images[3] = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/skybox/negy.png")); - defer images[3].deinit(); - images[4] = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/skybox/posz.png")); - defer images[4].deinit(); - images[5] = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/skybox/negz.png")); - defer images[5].deinit(); - - // Use the first image of the set for sizing - const img_size = gpu.Extent3D{ - .width = @intCast(u32, images[0].width), - .height = @intCast(u32, images[0].height), - }; - - // We set depth_or_array_layers to 6 here to indicate there are 6 images in this texture - const tex_size = gpu.Extent3D{ - .width = @intCast(u32, images[0].width), - .height = @intCast(u32, images[0].height), - .depth_or_array_layers = 6, - }; - - // Same as a regular texture, but with a Z of 6 (defined in tex_size) - const cube_texture = core.device.createTexture(&.{ - .size = tex_size, - .format = .rgba8_unorm, - .dimension = .dimension_2d, - .usage = .{ - .texture_binding = true, - .copy_dst = true, - .render_attachment = false, - }, - }); - - const data_layout = gpu.Texture.DataLayout{ - .bytes_per_row = @intCast(u32, images[0].width * 4), - .rows_per_image = @intCast(u32, images[0].height), - }; - - const encoder = core.device.createCommandEncoder(null); - - // We have to create a staging buffer, copy all the image data into the - // staging buffer at the correct Z offset, encode a command to copy - // the buffer to the texture for each image, then push it to the command - // queue - var staging_buff: [6]*gpu.Buffer = undefined; - var i: u32 = 0; - while (i < 6) : (i += 1) { - staging_buff[i] = core.device.createBuffer(&.{ - .usage = .{ .copy_src = true, .map_write = true }, - .size = @intCast(u64, images[0].width) * @intCast(u64, images[0].height) * @sizeOf(u32), - .mapped_at_creation = true, - }); - switch (images[i].pixels) { - .rgba32 => |pixels| { - // Map a section of the staging buffer - var staging_map = staging_buff[i].getMappedRange(u32, 0, @intCast(u64, images[i].width) * @intCast(u64, images[i].height)); - // Copy the image data into the mapped buffer - std.mem.copy(u32, staging_map.?, @ptrCast([]u32, @alignCast(@alignOf([]u32), pixels))); - // And release the mapping - staging_buff[i].unmap(); - }, - .rgb24 => |pixels| { - var staging_map = staging_buff[i].getMappedRange(u32, 0, @intCast(u64, images[i].width) * @intCast(u64, images[i].height)); - // In this case, we have to convert the data to rgba32 first - const data = try rgb24ToRgba32(core.allocator, pixels); - defer data.deinit(core.allocator); - std.mem.copy(u32, staging_map.?, @ptrCast([]u32, @alignCast(@alignOf([]u32), data.rgba32))); - staging_buff[i].unmap(); - }, - else => @panic("unsupported image color format"), - } - - // These define the source and target for the buffer to texture copy command - const copy_buff = gpu.ImageCopyBuffer{ - .layout = data_layout, - .buffer = staging_buff[i], - }; - const copy_tex = gpu.ImageCopyTexture{ - .texture = cube_texture, - .origin = gpu.Origin3D{ .x = 0, .y = 0, .z = i }, - }; - - // Encode the copy command, we do this for every image in the texture. - encoder.copyBufferToTexture(©_buff, ©_tex, &img_size); - } - // Now that the commands to copy our buffer data to the texture is filled, - // push the encoded commands over to the queue and execute to get the - // texture filled with the image data. - var command = encoder.finish(null); - encoder.release(); - queue.submit(&.{command}); - command.release(); - - // The textureView in the bind group needs dimension defined as "dimension_cube". - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = pipeline.getBindGroupLayout(0), - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - gpu.BindGroup.Entry.sampler(1, sampler), - gpu.BindGroup.Entry.textureView(2, cube_texture.createView(&gpu.TextureView.Descriptor{ .dimension = .dimension_cube })), - }, - }), - ); - - app.pipeline = pipeline; - app.queue = queue; - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group = bind_group; - app.depth_texture = null; - app.depth_texture_view = undefined; - - vs_module.release(); - fs_module.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.vertex_buffer.release(); - app.uniform_buffer.release(); - app.bind_group.release(); - app.depth_texture.?.release(); - app.depth_texture_view.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = .{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 0.0 }, - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - .depth_stencil_attachment = &.{ - .view = app.depth_texture_view, - .depth_clear_value = 1.0, - .depth_load_op = .clear, - .depth_store_op = .store, - }, - }); - - { - const time = timer.read(); - const aspect = @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height); - const proj = zm.perspectiveFovRh((2 * std.math.pi) / 5.0, aspect, 0.1, 3000); - const model = zm.mul( - zm.scaling(1000, 1000, 1000), - zm.rotationX(std.math.pi / 2.0 * 3.0), - ); - const view = zm.mul( - zm.mul( - zm.lookAtRh( - zm.f32x4(0, 0, 0, 1), - zm.f32x4(1, 0, 0, 1), - zm.f32x4(0, 0, 1, 0), - ), - zm.rotationY(time * 0.2), - ), - zm.rotationX((std.math.pi / 10.0) * std.math.sin(time)), - ); - - const mvp = zm.mul(zm.mul(zm.transpose(model), view), proj); - const ubo = UniformBufferObject{ .mat = mvp }; - - encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo}); - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - pass.setBindGroup(0, app.bind_group, &.{}); - pass.draw(vertices.len, 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} - -pub fn resize(app: *App, core: *mach.Core, width: u32, height: u32) !void { - // If window is resized, recreate depth buffer otherwise we cannot use it. - if (app.depth_texture != null) { - app.depth_texture.?.release(); - app.depth_texture_view.release(); - } - app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .size = gpu.Extent3D{ - .width = width, - .height = height, - }, - .format = .depth24_plus, - .usage = .{ - .render_attachment = true, - .texture_binding = true, - }, - }); - - app.depth_texture_view = app.depth_texture.?.createView(&gpu.TextureView.Descriptor{ - .format = .depth24_plus, - .dimension = .dimension_2d, - .array_layer_count = 1, - .mip_level_count = 1, - }); -} - -fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage { - const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len); - var i: usize = 0; - while (i < in.len) : (i += 1) { - out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 }; - } - return out; -} diff --git a/examples/cubemap/vert.wgsl b/examples/cubemap/vert.wgsl deleted file mode 100644 index 299033ae..00000000 --- a/examples/cubemap/vert.wgsl +++ /dev/null @@ -1,22 +0,0 @@ -struct Uniforms { - modelViewProjectionMatrix : mat4x4, -} -@binding(0) @group(0) var uniforms : Uniforms; - -struct VertexOutput { - @builtin(position) Position : vec4, - @location(0) fragUV : vec2, - @location(1) fragPosition: vec4, -} - -@vertex -fn main( - @location(0) position : vec4, - @location(1) uv : vec2 -) -> VertexOutput { - var output : VertexOutput; - output.Position = uniforms.modelViewProjectionMatrix * position; - output.fragUV = uv; - output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); - return output; -} diff --git a/examples/ecs-app/main.zig b/examples/ecs-app/main.zig deleted file mode 100644 index e30909f0..00000000 --- a/examples/ecs-app/main.zig +++ /dev/null @@ -1,53 +0,0 @@ -// Experimental ECS app example. Not yet ready for actual use. - -const std = @import("std"); -const mach = @import("mach"); -const gpu = mach.gpu; -const ecs = mach.ecs; - -// TODO: rename *ecs.World to *engine.Engine or something - -const renderer = @import("renderer.zig"); -const physics2d = @import("physics2d.zig"); - -// Define all the modules in our application. Modules can have components, systems, state, -// and/or global values in them. They can also send and receive messages to coordinate -// with each-other. -// -// Single-word module names (`.mach`, `.renderer`, etc.) are reserved for the application itself. -// -// Modules that come from libraries must be prefixed (e.g. `.bullet_physics`, `.ziglibs_box2d`) -// similar to GitHub repositories, to avoid conflicts with one another. Note that modules themselves -// will interact with the ECS using e.g. `.getComponent(.bullet_physics, .location)` internally and -// so cannot be renamed here. -// TODO: just make this a list so one cannot even think renaming is possible here -const modules = ecs.Modules(.{ - .mach = mach.module, - .renderer = renderer.module, - .physics2d = physics2d.module, -}); - -// Our Mach app, which tells Mach where to find our modules and init entry point. -pub const App = mach.App(modules, init); - -pub fn init(engine: *ecs.World(modules)) !void { - // The Mach .core is where we set window options, etc. - const core = engine.get(.mach, .core); - try core.setOptions(.{ .title = "Hello, ECS!" }); - - // We can get the GPU device: - const device = engine.get(.mach, .device); - _ = device; // TODO: actually show off using the GPU device - - // We can create entities, and set components on them. Note that components live in a module - // namespace, so we set the `.renderer, .location` component which is different than the - // `.physics2d, .location` component. - - // TODO: cut out the `.entities.` in this API to make it more brief - const player = try engine.entities.new(); - try engine.entities.setComponent(player, .renderer, .location, .{ .x = 0, .y = 0, .z = 0 }); - try engine.entities.setComponent(player, .physics2d, .location, .{ .x = 0, .y = 0 }); - - // TODO: there could be an entities wrapper to interact with a single namespace so you don't - // have to pass it in as a parameter always? -} diff --git a/examples/ecs-app/physics2d.zig b/examples/ecs-app/physics2d.zig deleted file mode 100644 index 316f1c08..00000000 --- a/examples/ecs-app/physics2d.zig +++ /dev/null @@ -1,26 +0,0 @@ -const mach = @import("mach"); -const ecs = mach.ecs; -const std = @import("std"); - -pub const Message = ecs.Messages(.{ - .tick = void, -}); - -pub const module = ecs.Module(.{ - .components = .{ - .location = Vec2, - .rotation = Vec2, - .velocity = Vec2, - }, - .messages = Message, - .update = update, -}); - -pub const Vec2 = extern struct { x: f32, y: f32 }; - -fn update(msg: Message) void { - switch (msg) { - // TODO: implement queries, ability to set components, etc. - .tick => std.log.debug("physics tick!", .{}), - } -} diff --git a/examples/ecs-app/renderer.zig b/examples/ecs-app/renderer.zig deleted file mode 100644 index d1797c27..00000000 --- a/examples/ecs-app/renderer.zig +++ /dev/null @@ -1,13 +0,0 @@ -const mach = @import("mach"); -const ecs = mach.ecs; - -pub const module = ecs.Module(.{ - .components = .{ - .location = Vec3, - .rotation = Vec3, - }, - // TODO: there would be systems that we register here. Functions that iterate over entities - // with renderer components like `.geometry` and render them for example! -}); - -pub const Vec3 = extern struct { x: f32, y: f32, z: f32 }; diff --git a/examples/fractal-cube/cube_mesh.zig b/examples/fractal-cube/cube_mesh.zig deleted file mode 100644 index f26c75ac..00000000 --- a/examples/fractal-cube/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(4, f32), - col: @Vector(4, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, -}; diff --git a/examples/fractal-cube/frag.wgsl b/examples/fractal-cube/frag.wgsl deleted file mode 100755 index 4bf24e0b..00000000 --- a/examples/fractal-cube/frag.wgsl +++ /dev/null @@ -1,12 +0,0 @@ -@binding(1) @group(0) var mySampler: sampler; -@binding(2) @group(0) var myTexture: texture_2d; - -@fragment fn main( - @location(0) fragUV: vec2, - @location(1) fragPosition: vec4 -) -> @location(0) vec4 { - let texColor = textureSample(myTexture, mySampler, fragUV * 0.8 + vec2(0.1, 0.1)); - let f = f32(length(texColor.rgb - vec3(0.5, 0.5, 0.5)) < 0.01); - return (1.0 - f) * texColor + f * fragPosition; - // return vec4(texColor.rgb,1.0); -} diff --git a/examples/fractal-cube/main.zig b/examples/fractal-cube/main.zig deleted file mode 100755 index 202fd949..00000000 --- a/examples/fractal-cube/main.zig +++ /dev/null @@ -1,359 +0,0 @@ -//! To get the effect we want, we need a texture on which to render; -//! we can't use the swapchain texture directly, but we can get the effect -//! by doing the same render pass twice, on the texture and the swapchain. -//! We also need a second texture to use on the cube (after the render pass -//! it needs to copy the other texture.) We can't use the same texture since -//! it would interfere with the synchronization on the gpu during the render pass. -//! This demo currently does not work on opengl, because core.current_desc.width/height, -//! are set to 0 after core.init() and because webgpu does not implement copyTextureToTexture, -//! for opengl - -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; - -pub const App = @This(); - -const UniformBufferObject = struct { - mat: zm.Mat, -}; - -var timer: mach.Timer = undefined; - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, -depth_texture: ?*gpu.Texture, -depth_texture_view: *gpu.TextureView, -cube_texture: *gpu.Texture, -cube_texture_view: *gpu.TextureView, -cube_texture_render: *gpu.Texture, -cube_texture_view_render: *gpu.TextureView, -sampler: *gpu.Sampler, -bgl: *gpu.BindGroupLayout, - -pub fn init(app: *App, core: *mach.Core) !void { - timer = try mach.Timer.start(); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .attributes = &vertex_attributes, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgle_buffer = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); - const bgle_sampler = gpu.BindGroupLayout.Entry.sampler(1, .{ .fragment = true }, .filtering); - const bgle_textureview = gpu.BindGroupLayout.Entry.texture(2, .{ .fragment = true }, .float, .dimension_2d, false); - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ bgle_buffer, bgle_sampler, bgle_textureview }, - }), - ); - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .depth_stencil = &.{ - .format = .depth24_plus, - .depth_write_enabled = true, - .depth_compare = .less, - }, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }), - .primitive = .{ - .cull_mode = .back, - }, - }; - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject), - .mapped_at_creation = false, - }); - - // The texture to put on the cube - const cube_texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .usage = .{ .texture_binding = true, .copy_dst = true }, - .size = .{ .width = core.current_desc.width, .height = core.current_desc.height }, - .format = core.swap_chain_format, - }); - // The texture on which we render - const cube_texture_render = core.device.createTexture(&gpu.Texture.Descriptor{ - .usage = .{ .render_attachment = true, .copy_src = true }, - .size = .{ .width = core.current_desc.width, .height = core.current_desc.height }, - .format = core.swap_chain_format, - }); - - const sampler = core.device.createSampler(&gpu.Sampler.Descriptor{ - .mag_filter = .linear, - .min_filter = .linear, - }); - - const cube_texture_view = cube_texture.createView(&gpu.TextureView.Descriptor{ - .format = core.swap_chain_format, - .dimension = .dimension_2d, - .mip_level_count = 1, - .array_layer_count = 1, - }); - const cube_texture_view_render = cube_texture_render.createView(&gpu.TextureView.Descriptor{ - .format = core.swap_chain_format, - .dimension = .dimension_2d, - .mip_level_count = 1, - .array_layer_count = 1, - }); - - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - gpu.BindGroup.Entry.sampler(1, sampler), - gpu.BindGroup.Entry.textureView(2, cube_texture_view), - }, - }), - ); - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = core.device.getQueue(); - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group = bind_group; - app.depth_texture = null; - app.depth_texture_view = undefined; - app.cube_texture = cube_texture; - app.cube_texture_view = cube_texture_view; - app.cube_texture_render = cube_texture_render; - app.cube_texture_view_render = cube_texture_view_render; - app.sampler = sampler; - app.bgl = bgl; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.bgl.release(); - app.vertex_buffer.release(); - app.uniform_buffer.release(); - app.cube_texture.release(); - app.cube_texture_render.release(); - app.sampler.release(); - app.cube_texture_view.release(); - app.cube_texture_view_render.release(); - app.bind_group.release(); - app.depth_texture.?.release(); - app.depth_texture_view.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const cube_view = app.cube_texture_view_render; - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - - const cube_color_attachment = gpu.RenderPassColorAttachment{ - .view = cube_view, - .clear_value = gpu.Color{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 }, - .load_op = .clear, - .store_op = .store, - }; - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = gpu.Color{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 }, - .load_op = .clear, - .store_op = .store, - }; - - const depth_stencil_attachment = gpu.RenderPassDepthStencilAttachment{ - .view = app.depth_texture_view, - .depth_load_op = .clear, - .depth_store_op = .store, - .depth_clear_value = 1.0, - }; - - const encoder = core.device.createCommandEncoder(null); - const cube_render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{cube_color_attachment}, - .depth_stencil_attachment = &depth_stencil_attachment, - }); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - .depth_stencil_attachment = &depth_stencil_attachment, - }); - - { - const time = timer.read(); - const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0))); - const view = zm.lookAtRh( - zm.f32x4(0, -4, 0, 1), - zm.f32x4(0, 0, 0, 1), - zm.f32x4(0, 0, 1, 0), - ); - const proj = zm.perspectiveFovRh( - (std.math.pi * 2.0 / 5.0), - @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height), - 1, - 100, - ); - const ubo = UniformBufferObject{ - .mat = zm.transpose(zm.mul(zm.mul(model, view), proj)), - }; - encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo}); - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setBindGroup(0, app.bind_group, &.{0}); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - pass.draw(vertices.len, 1, 0, 0); - pass.end(); - pass.release(); - - encoder.copyTextureToTexture( - &gpu.ImageCopyTexture{ - .texture = app.cube_texture_render, - }, - &gpu.ImageCopyTexture{ - .texture = app.cube_texture, - }, - &.{ .width = core.current_desc.width, .height = core.current_desc.height }, - ); - - const cube_pass = encoder.beginRenderPass(&cube_render_pass_info); - cube_pass.setPipeline(app.pipeline); - cube_pass.setBindGroup(0, app.bind_group, &.{0}); - cube_pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - cube_pass.draw(vertices.len, 1, 0, 0); - cube_pass.end(); - cube_pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} - -pub fn resize(app: *App, core: *mach.Core, width: u32, height: u32) !void { - if (app.depth_texture != null) { - app.depth_texture.?.release(); - app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .usage = .{ .render_attachment = true }, - .size = .{ .width = width, .height = height }, - .format = .depth24_plus, - }); - - app.cube_texture.release(); - app.cube_texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .usage = .{ .texture_binding = true, .copy_dst = true }, - .size = .{ .width = width, .height = height }, - .format = core.swap_chain_format, - }); - app.cube_texture_render.release(); - app.cube_texture_render = core.device.createTexture(&gpu.Texture.Descriptor{ - .usage = .{ .render_attachment = true, .copy_src = true }, - .size = .{ .width = width, .height = height }, - .format = core.swap_chain_format, - }); - - app.depth_texture_view.release(); - app.depth_texture_view = app.depth_texture.?.createView(&gpu.TextureView.Descriptor{ - .format = .depth24_plus, - .dimension = .dimension_2d, - .array_layer_count = 1, - .mip_level_count = 1, - }); - - app.cube_texture_view.release(); - app.cube_texture_view = app.cube_texture.createView(&gpu.TextureView.Descriptor{ - .format = core.swap_chain_format, - .dimension = .dimension_2d, - .mip_level_count = 1, - .array_layer_count = 1, - }); - app.cube_texture_view_render.release(); - app.cube_texture_view_render = app.cube_texture_render.createView(&gpu.TextureView.Descriptor{ - .format = core.swap_chain_format, - .dimension = .dimension_2d, - .mip_level_count = 1, - .array_layer_count = 1, - }); - - app.bind_group.release(); - app.bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = app.bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, app.uniform_buffer, 0, @sizeOf(UniformBufferObject)), - gpu.BindGroup.Entry.sampler(1, app.sampler), - gpu.BindGroup.Entry.textureView(2, app.cube_texture_view), - }, - }), - ); - } else { - app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .usage = .{ .render_attachment = true }, - .size = .{ .width = width, .height = height }, - .format = .depth24_plus, - }); - app.depth_texture_view = app.depth_texture.?.createView(&gpu.TextureView.Descriptor{ - .format = .depth24_plus, - .dimension = .dimension_2d, - .array_layer_count = 1, - .mip_level_count = 1, - }); - } -} diff --git a/examples/fractal-cube/vert.wgsl b/examples/fractal-cube/vert.wgsl deleted file mode 100755 index 3bdc4897..00000000 --- a/examples/fractal-cube/vert.wgsl +++ /dev/null @@ -1,22 +0,0 @@ -struct Uniforms { - matrix : mat4x4, -}; - -@binding(0) @group(0) var ubo : Uniforms; - -struct VertexOut { - @builtin(position) Position : vec4, - @location(0) fragUV : vec2, - @location(1) fragPosition: vec4, -} - -@vertex fn main( - @location(0) position : vec4, - @location(1) uv: vec2 -) -> VertexOut { - var output : VertexOut; - output.Position = position * ubo.matrix; - output.fragUV = uv; - output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); - return output; -} diff --git a/examples/gkurve/LICENSE.atlas b/examples/gkurve/LICENSE.atlas deleted file mode 100644 index fd45bf4c..00000000 --- a/examples/gkurve/LICENSE.atlas +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2022 Mitchell Hashimoto - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/gkurve/LICENSE.tesselator b/examples/gkurve/LICENSE.tesselator deleted file mode 100644 index b7e66b1d..00000000 --- a/examples/gkurve/LICENSE.tesselator +++ /dev/null @@ -1 +0,0 @@ -TODO: add license, the tesselator implementation comes from https://github.com/fubark/cosmic/blob/master/graphics/src/tessellator.zig diff --git a/examples/gkurve/assets b/examples/gkurve/assets deleted file mode 160000 index b5a84047..00000000 --- a/examples/gkurve/assets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b5a8404715e6cfc57e66c4a7cc0625e64b3b3c56 diff --git a/examples/gkurve/atlas.zig b/examples/gkurve/atlas.zig deleted file mode 100644 index c7db1a42..00000000 --- a/examples/gkurve/atlas.zig +++ /dev/null @@ -1,325 +0,0 @@ -//! Implements a texture atlas (https://en.wikipedia.org/wiki/Texture_atlas). -//! -//! The implementation is based on "A Thousand Ways to Pack the Bin - A -//! Practical Approach to Two-Dimensional Rectangle Bin Packing" by Jukka -//! Jylänki. This specific implementation is based heavily on -//! Nicolas P. Rougier's freetype-gl project as well as Jukka's C++ -//! implementation: https://github.com/juj/RectangleBinPack -//! -//! Limitations that are easy to fix, but I didn't need them: -//! -//! * Written data must be packed, no support for custom strides. -//! * Texture is always a square, no ability to set width != height. Note -//! that regions written INTO the atlas do not have to be square, only -//! the full atlas texture itself. -//! - -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const testing = std.testing; - -const Node = struct { - x: u32, - y: u32, - width: u32, -}; - -pub const Error = error{ - /// Atlas cannot fit the desired region. You must enlarge the atlas. - AtlasFull, -}; - -/// A region within the texture atlas. These can be acquired using the -/// "reserve" function. A region reservation is required to write data. -pub const Region = struct { - x: u32, - y: u32, - width: u32, - height: u32, - - pub fn getUVData(region: Region, atlas_float_size: f32) UVData { - return .{ - .bottom_left = .{ @intToFloat(f32, region.x) / atlas_float_size, (atlas_float_size - @intToFloat(f32, region.y + region.height)) / atlas_float_size }, - .width_and_height = .{ @intToFloat(f32, region.width) / atlas_float_size, @intToFloat(f32, region.height) / atlas_float_size }, - }; - } -}; - -pub const UVData = extern struct { - bottom_left: @Vector(2, f32), - width_and_height: @Vector(2, f32), -}; - -pub fn Atlas(comptime T: type) type { - return struct { - /// Data is the raw texture data. - data: []T, - - /// Width and height of the atlas texture. The current implementation is - /// always square so this is both the width and the height. - size: u32 = 0, - - /// The nodes (rectangles) of available space. - nodes: std.ArrayListUnmanaged(Node) = .{}, - - const Self = @This(); - - pub fn init(alloc: Allocator, size: u32) !Self { - var result = Self{ - .data = try alloc.alloc(T, size * size), - .size = size, - .nodes = .{}, - }; - - // TODO: figure out optimal prealloc based on real world usage - try result.nodes.ensureUnusedCapacity(alloc, 64); - - // This sets up our initial state - result.clear(); - - return result; - } - - pub fn deinit(self: *Self, alloc: Allocator) void { - self.nodes.deinit(alloc); - alloc.free(self.data); - self.* = undefined; - } - - /// Reserve a region within the atlas with the given width and height. - /// - /// May allocate to add a new rectangle into the internal list of rectangles. - /// This will not automatically enlarge the texture if it is full. - pub fn reserve(self: *Self, alloc: Allocator, width: u32, height: u32) !Region { - // x, y are populated within :best_idx below - var region: Region = .{ .x = 0, .y = 0, .width = width, .height = height }; - - // Find the location in our nodes list to insert the new node for this region. - var best_idx: usize = best_idx: { - var best_height: u32 = std.math.maxInt(u32); - var best_width: u32 = best_height; - var chosen: ?usize = null; - - var i: usize = 0; - while (i < self.nodes.items.len) : (i += 1) { - // Check if our region fits within this node. - const y = self.fit(i, width, height) orelse continue; - - const node = self.nodes.items[i]; - if ((y + height) < best_height or - ((y + height) == best_height and - (node.width > 0 and node.width < best_width))) - { - chosen = i; - best_width = node.width; - best_height = y + height; - region.x = node.x; - region.y = y; - } - } - - // If we never found a chosen index, the atlas cannot fit our region. - break :best_idx chosen orelse return Error.AtlasFull; - }; - - // Insert our new node for this rectangle at the exact best index - try self.nodes.insert(alloc, best_idx, .{ - .x = region.x, - .y = region.y + height, - .width = width, - }); - - // Optimize our rectangles - var i: usize = best_idx + 1; - while (i < self.nodes.items.len) : (i += 1) { - const node = &self.nodes.items[i]; - const prev = self.nodes.items[i - 1]; - if (node.x < (prev.x + prev.width)) { - const shrink = prev.x + prev.width - node.x; - node.x += shrink; - node.width -|= shrink; - if (node.width <= 0) { - _ = self.nodes.orderedRemove(i); - i -= 1; - continue; - } - } - - break; - } - self.merge(); - - return region; - } - - /// Attempts to fit a rectangle of width x height into the node at idx. - /// The return value is the y within the texture where the rectangle can be - /// placed. The x is the same as the node. - fn fit(self: Self, idx: usize, width: u32, height: u32) ?u32 { - // If the added width exceeds our texture size, it doesn't fit. - const node = self.nodes.items[idx]; - if ((node.x + width) > (self.size - 1)) return null; - - // Go node by node looking for space that can fit our width. - var y = node.y; - var i = idx; - var width_left = width; - while (width_left > 0) : (i += 1) { - const n = self.nodes.items[i]; - if (n.y > y) y = n.y; - - // If the added height exceeds our texture size, it doesn't fit. - if ((y + height) > (self.size - 1)) return null; - - width_left -|= n.width; - } - - return y; - } - - /// Merge adjacent nodes with the same y value. - fn merge(self: *Self) void { - var i: usize = 0; - while (i < self.nodes.items.len - 1) { - const node = &self.nodes.items[i]; - const next = self.nodes.items[i + 1]; - if (node.y == next.y) { - node.width += next.width; - _ = self.nodes.orderedRemove(i + 1); - continue; - } - - i += 1; - } - } - - /// Set the data associated with a reserved region. The data is expected - /// to fit exactly within the region. - pub fn set(self: *Self, reg: Region, data: []const T) void { - assert(reg.x < (self.size - 1)); - assert((reg.x + reg.width) <= (self.size - 1)); - assert(reg.y < (self.size - 1)); - assert((reg.y + reg.height) <= (self.size - 1)); - - var i: u32 = 0; - while (i < reg.height) : (i += 1) { - const tex_offset = ((reg.y + i) * self.size) + reg.x; - const data_offset = i * reg.width; - std.mem.copy( - T, - self.data[tex_offset..], - data[data_offset .. data_offset + reg.width], - ); - } - } - - // Grow the texture to the new size, preserving all previously written data. - pub fn grow(self: *Self, alloc: Allocator, size_new: u32) Allocator.Error!void { - assert(size_new >= self.size); - if (size_new == self.size) return; - - // Preserve our old values so we can copy the old data - const data_old = self.data; - const size_old = self.size; - - self.data = try alloc.alloc(T, size_new * size_new); - defer alloc.free(data_old); // Only defer after new data succeeded - self.size = size_new; // Only set size after new alloc succeeded - std.mem.set(T, self.data, std.mem.zeroes(T)); - self.set(.{ - .x = 0, // don't bother skipping border so we can avoid strides - .y = 1, // skip the first border row - .width = size_old, - .height = size_old - 2, // skip the last border row - }, data_old[size_old..]); - - // Add our new rectangle for our added righthand space - try self.nodes.append(alloc, .{ - .x = size_old - 1, - .y = 1, - .width = size_new - size_old, - }); - } - - // Empty the atlas. This doesn't reclaim any previously allocated memory. - pub fn clear(self: *Self) void { - std.mem.set(T, self.data, std.mem.zeroes(T)); - self.nodes.clearRetainingCapacity(); - - // Add our initial rectangle. This is the size of the full texture - // and is the initial rectangle we fit our regions in. We keep a 1px border - // to avoid artifacting when sampling the texture. - self.nodes.appendAssumeCapacity(.{ .x = 1, .y = 1, .width = self.size - 2 }); - } - }; -} - -test "exact fit" { - const alloc = testing.allocator; - var atlas = try Atlas(u32).init(alloc, 34); // +2 for 1px border - defer atlas.deinit(alloc); - - _ = try atlas.reserve(alloc, 32, 32); - try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1)); -} - -test "doesnt fit" { - const alloc = testing.allocator; - var atlas = try Atlas(f32).init(alloc, 32); - defer atlas.deinit(alloc); - - // doesn't fit due to border - try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 32, 32)); -} - -test "fit multiple" { - const alloc = testing.allocator; - var atlas = try Atlas(u16).init(alloc, 32); - defer atlas.deinit(alloc); - - _ = try atlas.reserve(alloc, 15, 30); - _ = try atlas.reserve(alloc, 15, 30); - try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1)); -} - -test "writing data" { - const alloc = testing.allocator; - var atlas = try Atlas(u64).init(alloc, 32); - defer atlas.deinit(alloc); - - const reg = try atlas.reserve(alloc, 2, 2); - atlas.set(reg, &[_]u64{ 1, 2, 3, 4 }); - - // 33 because of the 1px border and so on - try testing.expectEqual(@as(u64, 1), atlas.data[33]); - try testing.expectEqual(@as(u64, 2), atlas.data[34]); - try testing.expectEqual(@as(u64, 3), atlas.data[65]); - try testing.expectEqual(@as(u64, 4), atlas.data[66]); -} - -test "grow" { - const alloc = testing.allocator; - var atlas = try Atlas(u32).init(alloc, 4); // +2 for 1px border - defer atlas.deinit(alloc); - - const reg = try atlas.reserve(alloc, 2, 2); - try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1)); - - // Write some data so we can verify that growing doesn't mess it up - atlas.set(reg, &[_]u32{ 1, 2, 3, 4 }); - try testing.expectEqual(@as(u32, 1), atlas.data[5]); - try testing.expectEqual(@as(u32, 2), atlas.data[6]); - try testing.expectEqual(@as(u32, 3), atlas.data[9]); - try testing.expectEqual(@as(u32, 4), atlas.data[10]); - - // Expand by exactly 1 should fit our new 1x1 block. - try atlas.grow(alloc, atlas.size + 1); - _ = try atlas.reserve(alloc, 1, 1); - - // Ensure our data is still set. Not the offsets change due to size. - try testing.expectEqual(@as(u32, 1), atlas.data[atlas.size + 1]); - try testing.expectEqual(@as(u32, 2), atlas.data[atlas.size + 2]); - try testing.expectEqual(@as(u32, 3), atlas.data[atlas.size * 2 + 1]); - try testing.expectEqual(@as(u32, 4), atlas.data[atlas.size * 2 + 2]); -} diff --git a/examples/gkurve/data_structures/bit_array_list.zig b/examples/gkurve/data_structures/bit_array_list.zig deleted file mode 100644 index c2c2311c..00000000 --- a/examples/gkurve/data_structures/bit_array_list.zig +++ /dev/null @@ -1,67 +0,0 @@ -const std = @import("std"); - -// std.DynamicBitSet doesn't behave like std.ArrayList, it will realloc on every resize. -// For now provide a bitset api and use std.ArrayList(bool) as the implementation. -pub const BitArrayList = struct { - const Self = @This(); - - buf: std.ArrayList(bool), - - pub fn init(alloc: std.mem.Allocator) Self { - return .{ - .buf = std.ArrayList(bool).init(alloc), - }; - } - - pub fn deinit(self: Self) void { - self.buf.deinit(); - } - - pub fn clearRetainingCapacity(self: *Self) void { - self.buf.clearRetainingCapacity(); - } - - pub fn appendUnset(self: *Self) !void { - try self.buf.append(false); - } - - pub fn appendSet(self: *Self) !void { - try self.buf.append(true); - } - - pub fn isSet(self: Self, idx: usize) bool { - return self.buf.items[idx]; - } - - pub fn set(self: *Self, idx: usize) void { - self.buf.items[idx] = true; - } - - pub fn unset(self: *Self, idx: usize) void { - self.buf.items[idx] = false; - } - - pub fn setRange(self: *Self, start: usize, end: usize) void { - std.mem.set(bool, self.buf.items[start..end], true); - } - - pub fn unsetRange(self: *Self, start: usize, end: usize) void { - std.mem.set(bool, self.buf.items[start..end], false); - } - - pub fn resize(self: *Self, size: usize) !void { - try self.buf.resize(size); - } - - pub fn resizeFillNew(self: *Self, size: usize, comptime fill: bool) !void { - const start = self.buf.items.len; - try self.resize(size); - if (self.buf.items.len > start) { - if (fill) { - self.setRange(start, self.buf.items.len); - } else { - self.unsetRange(start, self.buf.items.len); - } - } - } -}; diff --git a/examples/gkurve/data_structures/compact.zig b/examples/gkurve/data_structures/compact.zig deleted file mode 100644 index 0b94c1e2..00000000 --- a/examples/gkurve/data_structures/compact.zig +++ /dev/null @@ -1,780 +0,0 @@ -const std = @import("std"); -const t = std.testing; -const BitArrayList = @import("bit_array_list.zig").BitArrayList; -const log = std.log.scoped(.compact); - -/// Useful for keeping elements closer together in memory when you're using a bunch of insert/delete, -/// while keeping realloc to a minimum and preserving the element's initial insert index. -/// Backed by std.ArrayList. -/// Item ids are reused once removed. -/// Items are assigned an id and have O(1) access time by id. -/// TODO: Iterating can be just as fast as a dense array if CompactIdGenerator kept a sorted list of freed id ranges. -/// Although that also means delete ops would need to be O(logn). -pub fn CompactUnorderedList(comptime Id: type, comptime T: type) type { - if (@typeInfo(Id).Int.signedness != .unsigned) { - @compileError("Unsigned id type required."); - } - return struct { - id_gen: CompactIdGenerator(Id), - - // TODO: Rename to buf. - data: std.ArrayList(T), - - // Keep track of whether an item exists at id in order to perform iteration. - // TODO: Rename to exists. - // TODO: Maybe the user should provide this if it's important. It would also simplify the api and remove optional return types. It also means iteration won't be possible. - data_exists: BitArrayList, - - const Self = @This(); - const Iterator = struct { - // The current id should reflect the id of the value returned from next or nextPtr. - cur_id: Id, - list: *const Self, - - fn init(list: *const Self) @This() { - return .{ - .cur_id = std.math.maxInt(Id), - .list = list, - }; - } - - pub fn reset(self: *@This()) void { - self.idx = std.math.maxInt(Id); - } - - pub fn nextPtr(self: *@This()) ?*T { - self.cur_id +%= 1; - while (true) { - if (self.cur_id < self.list.data.items.len) { - if (!self.list.data_exists.isSet(self.cur_id)) { - self.cur_id += 1; - continue; - } else { - return &self.list.data.items[self.cur_id]; - } - } else { - return null; - } - } - } - - pub fn next(self: *@This()) ?T { - self.cur_id +%= 1; - while (true) { - if (self.cur_id < self.list.data.items.len) { - if (!self.list.data_exists.isSet(self.cur_id)) { - self.cur_id += 1; - continue; - } else { - return self.list.data.items[self.cur_id]; - } - } else { - return null; - } - } - } - }; - - pub fn init(alloc: std.mem.Allocator) @This() { - const new = @This(){ - .id_gen = CompactIdGenerator(Id).init(alloc, 0), - .data = std.ArrayList(T).init(alloc), - .data_exists = BitArrayList.init(alloc), - }; - return new; - } - - pub fn deinit(self: Self) void { - self.id_gen.deinit(); - self.data.deinit(); - self.data_exists.deinit(); - } - - pub fn iterator(self: *const Self) Iterator { - return Iterator.init(self); - } - - // Returns the id of the item. - pub fn add(self: *Self, item: T) !Id { - const new_id = self.id_gen.getNextId(); - errdefer self.id_gen.deleteId(new_id); - - if (new_id >= self.data.items.len) { - try self.data.resize(new_id + 1); - try self.data_exists.resize(new_id + 1); - } - self.data.items[new_id] = item; - self.data_exists.set(new_id); - return new_id; - } - - pub fn set(self: *Self, id: Id, item: T) void { - self.data.items[id] = item; - } - - pub fn remove(self: *Self, id: Id) void { - self.data_exists.unset(id); - self.id_gen.deleteId(id); - } - - pub fn clearRetainingCapacity(self: *Self) void { - self.data_exists.clearRetainingCapacity(); - self.id_gen.clearRetainingCapacity(); - self.data.clearRetainingCapacity(); - } - - pub fn get(self: Self, id: Id) ?T { - if (self.has(id)) { - return self.data.items[id]; - } else return null; - } - - pub fn getNoCheck(self: Self, id: Id) T { - return self.data.items[id]; - } - - pub fn getPtr(self: *const Self, id: Id) ?*T { - if (self.has(id)) { - return &self.data.items[id]; - } else return null; - } - - pub fn getPtrNoCheck(self: Self, id: Id) *T { - return &self.data.items[id]; - } - - pub fn has(self: Self, id: Id) bool { - return self.data_exists.isSet(id); - } - - pub fn size(self: Self) usize { - return self.data.items.len - self.id_gen.next_ids.count; - } - }; -} - -test "CompactUnorderedList" { - { - // General test. - var arr = CompactUnorderedList(u32, u8).init(t.allocator); - defer arr.deinit(); - - _ = try arr.add(1); - const id = try arr.add(2); - _ = try arr.add(3); - arr.remove(id); - // Test adding to a removed slot. - _ = try arr.add(4); - const id2 = try arr.add(5); - // Test iterator skips removed slot. - arr.remove(id2); - - var iter = arr.iterator(); - try t.expectEqual(iter.next(), 1); - try t.expectEqual(iter.next(), 4); - try t.expectEqual(iter.next(), 3); - try t.expectEqual(iter.next(), null); - try t.expectEqual(arr.size(), 3); - } - { - // Empty test. - var arr = CompactUnorderedList(u32, u8).init(t.allocator); - defer arr.deinit(); - var iter = arr.iterator(); - try t.expectEqual(iter.next(), null); - try t.expectEqual(arr.size(), 0); - } -} - -/// Buffer is a CompactUnorderedList. -pub fn CompactSinglyLinkedList(comptime Id: type, comptime T: type) type { - const Null = CompactNull(Id); - const Node = CompactSinglyLinkedListNode(Id, T); - return struct { - const Self = @This(); - - first: Id, - nodes: CompactUnorderedList(Id, Node), - - pub fn init(alloc: std.mem.Allocator) Self { - return .{ - .first = Null, - .nodes = CompactUnorderedList(Id, Node).init(alloc), - }; - } - - pub fn deinit(self: Self) void { - self.nodes.deinit(); - } - - pub fn insertAfter(self: *Self, id: Id, data: T) !Id { - if (self.nodes.has(id)) { - const new = try self.nodes.add(.{ - .next = self.nodes.getNoCheck(id).next, - .data = data, - }); - self.nodes.getPtrNoCheck(id).next = new; - return new; - } else return error.NoElement; - } - - pub fn removeNext(self: *Self, id: Id) !bool { - if (self.nodes.has(id)) { - const at = self.nodes.getPtrNoCheck(id); - if (at.next != Null) { - const next = at.next; - at.next = self.nodes.getNoCheck(next).next; - self.nodes.remove(next); - return true; - } else return false; - } else return error.NoElement; - } - - pub fn getNode(self: *const Self, id: Id) ?Node { - return self.nodes.get(id); - } - - pub fn getNodeAssumeExists(self: *const Self, id: Id) Node { - return self.nodes.getNoCheck(id); - } - - pub fn get(self: *const Self, id: Id) ?T { - if (self.nodes.has(id)) { - return self.nodes.getNoCheck(id).data; - } else return null; - } - - pub fn getNoCheck(self: *const Self, id: Id) T { - return self.nodes.getNoCheck(id).data; - } - - pub fn getAt(self: *const Self, idx: usize) Id { - var i: u32 = 0; - var cur = self.first.?; - while (i != idx) : (i += 1) { - cur = self.getNext(cur).?; - } - return cur; - } - - pub fn getFirst(self: *const Self) Id { - return self.first; - } - - pub fn getNext(self: Self, id: Id) ?Id { - if (self.nodes.has(id)) { - return self.nodes.getNoCheck(id).next; - } else return null; - } - - pub fn prepend(self: *Self, data: T) !Id { - const node = Node{ - .next = self.first, - .data = data, - }; - self.first = try self.nodes.add(node); - return self.first; - } - - pub fn removeFirst(self: *Self) bool { - if (self.first != Null) { - const next = self.getNodeAssumeExists(self.first).next; - self.nodes.remove(self.first); - self.first = next; - return true; - } else return false; - } - }; -} - -test "CompactSinglyLinkedList" { - const Null = CompactNull(u32); - { - // General test. - var list = CompactSinglyLinkedList(u32, u8).init(t.allocator); - defer list.deinit(); - - const first = try list.prepend(1); - var last = first; - last = try list.insertAfter(last, 2); - last = try list.insertAfter(last, 3); - // Test remove next. - _ = try list.removeNext(first); - // Test remove first. - _ = list.removeFirst(); - - var id = list.getFirst(); - try t.expectEqual(list.get(id), 3); - id = list.getNext(id).?; - try t.expectEqual(id, Null); - } - { - // Empty test. - var list = CompactSinglyLinkedList(u32, u8).init(t.allocator); - defer list.deinit(); - try t.expectEqual(list.getFirst(), Null); - } -} - -/// Id should be an unsigned integer type. -/// Max value of Id is used to indicate null. (An optional would increase the struct size.) -pub fn CompactSinglyLinkedListNode(comptime Id: type, comptime T: type) type { - return struct { - next: Id, - data: T, - }; -} - -pub fn CompactNull(comptime Id: type) Id { - return comptime std.math.maxInt(Id); -} - -/// Stores multiple linked lists together in memory. -pub fn CompactManySinglyLinkedList(comptime ListId: type, comptime Index: type, comptime T: type) type { - const Node = CompactSinglyLinkedListNode(Index, T); - const Null = CompactNull(Index); - return struct { - const Self = @This(); - - const List = struct { - head: ?Index, - }; - - nodes: CompactUnorderedList(Index, Node), - lists: CompactUnorderedList(ListId, List), - - pub fn init(alloc: std.mem.Allocator) Self { - return .{ - .nodes = CompactUnorderedList(Index, Node).init(alloc), - .lists = CompactUnorderedList(ListId, List).init(alloc), - }; - } - - pub fn deinit(self: Self) void { - self.nodes.deinit(); - self.lists.deinit(); - } - - // Returns detached item. - pub fn detachAfter(self: *Self, id: Index) !Index { - if (self.nodes.has(id)) { - const item = self.getNodePtrAssumeExists(id); - const detached = item.next; - item.next = Null; - return detached; - } else return error.NoElement; - } - - pub fn insertAfter(self: *Self, id: Index, data: T) !Index { - if (self.nodes.has(id)) { - const new = try self.nodes.add(.{ - .next = self.nodes.getNoCheck(id).next, - .data = data, - }); - self.nodes.getPtrNoCheck(id).next = new; - return new; - } else return error.NoElement; - } - - pub fn setDetachedToEnd(self: *Self, id: Index, detached_id: Index) void { - const item = self.nodes.getPtr(id).?; - item.next = detached_id; - } - - pub fn addListWithDetachedHead(self: *Self, id: Index) !ListId { - return self.lists.add(.{ .head = id }); - } - - pub fn addListWithHead(self: *Self, data: T) !ListId { - const item_id = try self.addDetachedItem(data); - return self.addListWithDetachedHead(item_id); - } - - pub fn addEmptyList(self: *Self) !ListId { - return self.lists.add(.{ .head = Null }); - } - - pub fn addDetachedItem(self: *Self, data: T) !Index { - return try self.nodes.add(.{ - .next = Null, - .data = data, - }); - } - - pub fn prepend(self: *Self, list_id: ListId, data: T) !Index { - const list = self.getList(list_id); - const item = Node{ - .next = list.first, - .data = data, - }; - list.first = try self.nodes.add(item); - return list.first.?; - } - - pub fn removeFirst(self: *Self, list_id: ListId) bool { - const list = self.getList(list_id); - if (list.first == null) { - return false; - } else { - const next = self.getNext(list.first.?); - self.nodes.remove(list.first.?); - list.first = next; - return true; - } - } - - pub fn removeNext(self: *Self, id: Index) !bool { - if (self.nodes.has(id)) { - const at = self.nodes.getPtrNoCheck(id); - if (at.next != Null) { - const next = at.next; - at.next = self.nodes.getNoCheck(next).next; - self.nodes.remove(next); - return true; - } else return false; - } else return error.NoElement; - } - - pub fn removeDetached(self: *Self, id: Index) void { - self.nodes.remove(id); - } - - pub fn getListPtr(self: *const Self, id: ListId) *List { - return self.lists.getPtr(id); - } - - pub fn getListHead(self: *const Self, id: ListId) ?Index { - if (self.lists.has(id)) { - return self.lists.getNoCheck(id).head; - } else return null; - } - - pub fn findInList(self: Self, list_id: ListId, ctx: anytype, pred: fn (ctx: @TypeOf(ctx), buf: Self, item_id: Index) bool) ?Index { - var id = self.getListHead(list_id) orelse return null; - while (id != Null) { - if (pred(ctx, self, id)) { - return id; - } - id = self.getNextIdNoCheck(id); - } - return null; - } - - pub fn has(self: Self, id: Index) bool { - return self.nodes.has(id); - } - - pub fn getNode(self: Self, id: Index) ?Node { - return self.nodes.get(id); - } - - pub fn getNodeAssumeExists(self: Self, id: Index) Node { - return self.nodes.getNoCheck(id); - } - - pub fn getNodePtr(self: Self, id: Index) ?*Node { - return self.nodes.getPtr(id); - } - - pub fn getNodePtrAssumeExists(self: Self, id: Index) *Node { - return self.nodes.getPtrNoCheck(id); - } - - pub fn get(self: Self, id: Index) ?T { - if (self.nodes.has(id)) { - return self.nodes.getNoCheck(id).data; - } else return null; - } - - pub fn getNoCheck(self: Self, id: Index) T { - return self.nodes.getNoCheck(id).data; - } - - pub fn getIdAt(self: Self, list_id: ListId, idx: usize) Index { - var i: u32 = 0; - var cur: Index = self.getListHead(list_id).?; - while (i != idx) : (i += 1) { - cur = self.getNextId(cur).?; - } - return cur; - } - - pub fn getPtr(self: Self, id: Index) ?*T { - if (self.nodes.has(id)) { - return &self.nodes.getPtrNoCheck(id).data; - } else return null; - } - - pub fn getPtrNoCheck(self: Self, id: Index) *T { - return &self.nodes.getPtrNoCheck(id).data; - } - - pub fn getNextId(self: Self, id: Index) ?Index { - if (self.nodes.get(id)) |node| { - return node.next; - } else return null; - } - - pub fn getNextIdNoCheck(self: Self, id: Index) Index { - return self.nodes.getNoCheck(id).next; - } - - pub fn getNextNode(self: Self, id: Index) ?Node { - if (self.getNext(id)) |next| { - return self.getNode(next); - } else return null; - } - - pub fn getNextData(self: *const Self, id: Index) ?T { - if (self.getNext(id)) |next| { - return self.get(next); - } else return null; - } - }; -} - -test "CompactManySinglyLinkedList" { - const Null = CompactNull(u32); - var lists = CompactManySinglyLinkedList(u32, u32, u32).init(t.allocator); - defer lists.deinit(); - - const list_id = try lists.addListWithHead(10); - const head = lists.getListHead(list_id).?; - - // Test detachAfter. - const after = try lists.insertAfter(head, 20); - try t.expectEqual(lists.getNextIdNoCheck(head), after); - try t.expectEqual(lists.detachAfter(head), after); - try t.expectEqual(lists.getNextIdNoCheck(head), Null); -} - -/// Reuses deleted ids. -/// Uses a fifo id buffer to get the next id if not empty, otherwise it uses the next id counter. -pub fn CompactIdGenerator(comptime T: type) type { - return struct { - const Self = @This(); - - start_id: T, - next_default_id: T, - next_ids: std.fifo.LinearFifo(T, .Dynamic), - - pub fn init(alloc: std.mem.Allocator, start_id: T) Self { - return .{ - .start_id = start_id, - .next_default_id = start_id, - .next_ids = std.fifo.LinearFifo(T, .Dynamic).init(alloc), - }; - } - - pub fn peekNextId(self: Self) T { - if (self.next_ids.readableLength() == 0) { - return self.next_default_id; - } else { - return self.next_ids.peekItem(0); - } - } - - pub fn getNextId(self: *Self) T { - if (self.next_ids.readableLength() == 0) { - defer self.next_default_id += 1; - return self.next_default_id; - } else { - return self.next_ids.readItem().?; - } - } - - pub fn clearRetainingCapacity(self: *Self) void { - self.next_default_id = self.start_id; - self.next_ids.head = 0; - self.next_ids.count = 0; - } - - pub fn deleteId(self: *Self, id: T) void { - self.next_ids.writeItem(id) catch unreachable; - } - - pub fn deinit(self: Self) void { - self.next_ids.deinit(); - } - }; -} - -test "CompactIdGenerator" { - var gen = CompactIdGenerator(u16).init(t.allocator, 1); - defer gen.deinit(); - try t.expectEqual(gen.getNextId(), 1); - try t.expectEqual(gen.getNextId(), 2); - gen.deleteId(1); - try t.expectEqual(gen.getNextId(), 1); - try t.expectEqual(gen.getNextId(), 3); -} - -/// Holds linked lists in a compact buffer. Does not keep track of list heads. -/// This might replace CompactManySinglyLinkedList. -pub fn CompactSinglyLinkedListBuffer(comptime Id: type, comptime T: type) type { - const Null = comptime CompactNull(Id); - const OptId = Id; - return struct { - const Self = @This(); - - pub const Node = CompactSinglyLinkedListNode(Id, T); - - nodes: CompactUnorderedList(Id, Node), - - pub fn init(alloc: std.mem.Allocator) Self { - return .{ - .nodes = CompactUnorderedList(Id, Node).init(alloc), - }; - } - - pub fn deinit(self: Self) void { - self.nodes.deinit(); - } - - pub fn clearRetainingCapacity(self: *Self) void { - self.nodes.clearRetainingCapacity(); - } - - pub fn getNode(self: Self, idx: Id) ?Node { - return self.nodes.get(idx); - } - - pub fn getNodeNoCheck(self: Self, idx: Id) Node { - return self.nodes.getNoCheck(idx); - } - - pub fn getNodePtrNoCheck(self: Self, idx: Id) *Node { - return self.nodes.getPtrNoCheck(idx); - } - - pub fn iterator(self: Self) CompactUnorderedList(Id, Node).Iterator { - return self.nodes.iterator(); - } - - pub fn iterFirstNoCheck(self: Self) Id { - var iter = self.nodes.iterator(); - _ = iter.next(); - return iter.cur_id; - } - - pub fn iterFirstValueNoCheck(self: Self) T { - var iter = self.nodes.iterator(); - return iter.next().?.data; - } - - pub fn size(self: Self) usize { - return self.nodes.size(); - } - - pub fn getLast(self: Self, id: Id) ?Id { - if (id == Null) { - return null; - } - if (self.nodes.has(id)) { - var cur = id; - while (cur != Null) { - const next = self.getNextNoCheck(cur); - if (next == Null) { - return cur; - } - cur = next; - } - unreachable; - } else return null; - } - - pub fn get(self: Self, id: Id) ?T { - if (self.nodes.has(id)) { - return self.nodes.getNoCheck(id).data; - } else return null; - } - - pub fn getNoCheck(self: Self, idx: Id) T { - return self.nodes.getNoCheck(idx).data; - } - - pub fn getPtrNoCheck(self: Self, idx: Id) *T { - return &self.nodes.getPtrNoCheck(idx).data; - } - - pub fn getNextNoCheck(self: Self, id: Id) OptId { - return self.nodes.getNoCheck(id).next; - } - - pub fn getNext(self: Self, id: Id) ?OptId { - if (self.nodes.has(id)) { - return self.nodes.getNoCheck(id).next; - } else return null; - } - - /// Adds a new head node. - pub fn add(self: *Self, data: T) !Id { - return try self.nodes.add(.{ - .next = Null, - .data = data, - }); - } - - pub fn insertBeforeHead(self: *Self, head_id: Id, data: T) !Id { - if (self.nodes.has(head_id)) { - return try self.nodes.add(.{ - .next = head_id, - .data = data, - }); - } else return error.NoElement; - } - - pub fn insertBeforeHeadNoCheck(self: *Self, head_id: Id, data: T) !Id { - return try self.nodes.add(.{ - .next = head_id, - .data = data, - }); - } - - pub fn insertAfter(self: *Self, id: Id, data: T) !Id { - if (self.nodes.has(id)) { - const new = try self.nodes.add(.{ - .next = self.nodes.getNoCheck(id).next, - .data = data, - }); - self.nodes.getPtrNoCheck(id).next = new; - return new; - } else return error.NoElement; - } - - pub fn removeAfter(self: *Self, id: Id) !void { - if (self.nodes.has(id)) { - const next = self.getNextNoCheck(id); - if (next != Null) { - const next_next = self.getNextNoCheck(next); - self.nodes.getNoCheck(id).next = next_next; - self.nodes.remove(next); - } - } else return error.NoElement; - } - - pub fn removeAssumeNoPrev(self: *Self, id: Id) !void { - if (self.nodes.has(id)) { - self.nodes.remove(id); - } else return error.NoElement; - } - }; -} - -test "CompactSinglyLinkedListBuffer" { - var buf = CompactSinglyLinkedListBuffer(u32, u32).init(t.allocator); - defer buf.deinit(); - - const head = try buf.add(1); - try t.expectEqual(buf.get(head).?, 1); - try t.expectEqual(buf.getNoCheck(head), 1); - try t.expectEqual(buf.getNode(head).?.data, 1); - try t.expectEqual(buf.getNodeNoCheck(head).data, 1); - - const second = try buf.insertAfter(head, 2); - try t.expectEqual(buf.getNodeNoCheck(head).next, second); - try t.expectEqual(buf.getNoCheck(second), 2); - - try buf.removeAssumeNoPrev(head); - try t.expectEqual(buf.get(head), null); -} diff --git a/examples/gkurve/data_structures/rb_tree.zig b/examples/gkurve/data_structures/rb_tree.zig deleted file mode 100644 index b93eece8..00000000 --- a/examples/gkurve/data_structures/rb_tree.zig +++ /dev/null @@ -1,1390 +0,0 @@ -const std = @import("std"); -// const t = stdx.testing; -const t = std.testing; - -const log = std.log.scoped(.rb_tree); - -const Color = enum(u1) { - Black, - Red, -}; - -const CompactUnorderedList = @import("compact.zig").CompactUnorderedList; -const CompactNull = @import("compact.zig").CompactNull; - -/// Based on Zig's rb node pointer based implementation: -/// https://github.com/ziglang/std-lib-orphanage/blob/master/std/rb.zig -/// Deletion logic was redone from https://www.youtube.com/watch?v=CTvfzU_uNKE as guidance. -/// Correction for case 5 in the video: it should not have a parent black condition. -/// Visualize: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html -pub fn RbTree(comptime Id: type, comptime Value: type, comptime Context: type, comptime Compare: fn (Value, Value, Context) std.math.Order) type { - return struct { - root: OptId, - buf: CompactUnorderedList(Id, Node), - /// The current number of nodes. Does not count detached nodes. - size: usize, - ctx: Context, - - const Self = @This(); - const OptId = Id; - const NullId = CompactNull(Id); - - const Node = struct { - left: OptId, - right: OptId, - val: Value, - - parent: OptId, - color: Color, - - fn getParentOpt(self: Node) ?Id { - if (self.parent == NullId) { - return null; - } else { - return self.parent; - } - } - - fn isRoot(self: Node) bool { - return self.parent == NullId; - } - - fn setChild(self: *Node, child: Id, is_left: bool) void { - if (is_left) { - self.left = child; - } else { - self.right = child; - } - } - }; - - pub fn init(alloc: std.mem.Allocator, ctx: Context) Self { - return .{ - .root = NullId, - .buf = CompactUnorderedList(Id, Node).init(alloc), - .size = 0, - .ctx = ctx, - }; - } - - pub fn deinit(self: *Self) void { - self.buf.deinit(); - } - - pub fn clearRetainingCapacity(self: *Self) void { - self.buf.clearRetainingCapacity(); - self.root = NullId; - self.size = 0; - } - - /// Re-sorts a tree with a compare function. - pub fn sort(self: *Self, ctx: anytype, cmp: fn (Value, Value, @TypeOf(ctx)) std.math.Order) !void { - self.root = NullId; - var iter = self.buf.iterator(); - while (iter.next()) |node| { - self.buf.remove(iter.cur_id); - _ = try self.insertCustom(node.val, ctx, cmp); - } - } - - pub fn first(self: Self) ?Id { - if (self.root == NullId) { - return null; - } - var id = self.root; - var node = self.buf.getNoCheck(id); - while (node.left != NullId) { - id = node.left; - node = self.buf.getNoCheck(id); - } - return id; - } - - pub fn firstFrom(self: Self, node_id: Id) Id { - var id = node_id; - var node = self.buf.getNoCheck(id); - while (node.left != NullId) { - id = node.left; - node = self.buf.getNoCheck(id); - } - return id; - } - - pub fn last(self: Self) ?Id { - if (self.root == NullId) { - return null; - } - var id = self.root; - var node = self.buf.getNoCheck(id); - while (node.right != NullId) { - id = node.right; - node = self.buf.getNoCheck(id); - } - return id; - } - - pub fn allocValuesInOrder(self: Self, alloc: std.mem.Allocator) []const Value { - var vals = std.ArrayList(Value).initCapacity(alloc, self.size) catch unreachable; - var cur = self.first(); - while (cur) |id| { - vals.appendAssumeCapacity(self.get(id).?); - cur = self.getNext(id); - } - return vals.toOwnedSlice(); - } - - pub fn allocNodeIdsInOrder(self: Self, alloc: std.mem.Allocator) []const Id { - var node_ids = std.ArrayList(Id).initCapacity(alloc, self.size) catch unreachable; - var cur = self.first(); - while (cur) |id| { - node_ids.appendAssumeCapacity(id); - cur = self.getNext(id); - } - return node_ids.toOwnedSlice(); - } - - /// User code typically wouldn't need the node, but it is available for testing. - pub fn getNode(self: Self, id: Id) ?Node { - return self.buf.get(id); - } - - pub fn get(self: Self, id: Id) ?Value { - if (self.buf.get(id)) |node| { - return node.val; - } else return null; - } - - pub fn getNoCheck(self: Self, id: Id) Value { - return self.buf.getNoCheck(id).val; - } - - pub fn getPtr(self: Self, id: Id) ?*Value { - if (self.buf.getPtr(id)) |node| { - return &node.val; - } else return null; - } - - pub fn getPtrNoCheck(self: Self, id: Id) *Value { - return &self.buf.getPtrNoCheck(id).val; - } - - fn getSibling(self: Self, parent: *Node, child_id: Id) OptId { - _ = self; - if (parent.left == child_id) { - return parent.right; - } else { - return parent.left; - } - } - - // Replacement node (with it's children) are relinked in place of node . Node (with it's children) become detached from tree. - fn transplant(self: *Self, node_id: Id, node: *Node, r_id: Id, mb_rnode: ?*Node) void { - if (node_id == self.root) { - self.root = r_id; - } else { - const parent = self.buf.getPtrNoCheck(node.parent); - parent.setChild(r_id, parent.left == node_id); - } - if (mb_rnode) |rnode| { - rnode.parent = node.parent; - } - } - - pub fn removeDetached(self: *Self, node_id: Id) void { - self.buf.remove(node_id); - } - - pub fn remove(self: *Self, node_id: Id) anyerror!void { - try self.detach(node_id); - self.buf.remove(node_id); - } - - /// If node is not part of tree, error is returned. - /// Detaches the node from the tree, rebalances tree, but does not remove it. The caller is responsible for calling removeDetached later on. - pub fn detach(self: *Self, node_id: Id) anyerror!void { - try self.detachInternal(node_id); - self.size -= 1; - } - - fn detachInternal(self: *Self, node_id: Id) anyerror!void { - var node = self.buf.getPtr(node_id) orelse return error.DoesNotExist; - - var to_fix_nid: OptId = undefined; - - if (node.left == NullId) { - const mb_rnode: ?*Node = if (node.right != NullId) self.buf.getPtrNoCheck(node.right) else null; - self.transplant(node_id, node, node.right, mb_rnode); - to_fix_nid = node.right; - } else if (node.right == NullId) { - const mb_rnode: ?*Node = if (node.left != NullId) self.buf.getPtrNoCheck(node.left) else null; - self.transplant(node_id, node, node.left, mb_rnode); - to_fix_nid = node.left; - } else { - const r_id = self.firstFrom(node.right); - const r_node = self.buf.getPtrNoCheck(r_id); - - // Normally this would be a value copy but since ids are tied to their nodes, the replacement node is relinked in place of the target node. - // Transplant only relinks parent of replacement node. - const r_parent = r_node.parent; - self.transplant(node_id, node, r_id, r_node); - if (r_parent != node_id) { - const rp = self.buf.getPtrNoCheck(r_parent); - rp.setChild(node_id, rp.left == r_id); - node.parent = r_parent; - } else { - node.parent = r_id; - } - - // Swap colors. - const tmp_color = r_node.color; - r_node.color = node.color; - node.color = tmp_color; - - // Copy r_node value to node; a recursive call to delete node will be called. - var orig_val = node.val; - node.val = r_node.val; - defer { - // Revert back to node's original val in case the user wanted to only detach the node (and not remove it). - node.val = orig_val; - } - - // Relink r_node left and node left. - // Note: r_node shouldn't have a left child since firstFrom would have returned it the child instead. - self.buf.getPtrNoCheck(node.left).parent = r_id; - r_node.left = node.left; - node.left = NullId; - - // Relink r_node right and node right. - // Note: tmp_right can't be null since node should have two children. - const tmp_right = node.right; - if (r_node.right != NullId) { - self.buf.getPtrNoCheck(r_node.right).parent = node_id; - } - node.right = r_node.right; - if (tmp_right != r_id) { - self.buf.getPtrNoCheck(tmp_right).parent = r_id; - r_node.right = tmp_right; - } - - // Reduced to zero or one children case. Recurse once. - try self.detachInternal(node_id); - return; - } - - // If red was removed, it is done. - if (node.color == .Red) { - return; - } else { - // If black was removed and replacement is red. Paint replacement black and we are done. - if (to_fix_nid == NullId) { - // Double black. Perform fix. - self.detachFixUp(to_fix_nid, node.parent); - } else { - const r_node = self.buf.getPtrNoCheck(to_fix_nid); - if (r_node.color == .Red) { - // Can mark black and be done since a black was removed. - r_node.color = .Black; - } else { - // Double black. Perform fix. - self.detachFixUp(to_fix_nid, r_node.parent); - } - } - } - } - - /// Handle double black cases. - /// Assumes node is a double black. Since the node could be null, the current parent is also required. - fn detachFixUp(self: *Self, node_id: OptId, parent_id: OptId) void { - // Case 1: Root case. - if (parent_id == NullId) { - return; - } - - const parent = self.buf.getPtrNoCheck(parent_id); - const s_id = self.getSibling(parent, node_id); - const is_right_sibling = parent.left == node_id; - // Sibling must exist since node is a double black. - const sibling = self.buf.getPtrNoCheck(s_id); - const s_left: ?*Node = if (sibling.left != NullId) self.buf.getPtrNoCheck(sibling.left) else null; - const s_right: ?*Node = if (sibling.right != NullId) self.buf.getPtrNoCheck(sibling.right) else null; - const s_left_black = s_left == null or s_left.?.color == .Black; - const s_right_black = s_right == null or s_right.?.color == .Black; - - if (parent.color == .Black and sibling.color == .Red) { - if (s_left_black and s_right_black) { - if (is_right_sibling) { - // Case 2: parent is black, right sibling is red and has two black children. - self.rotateLeft(parent_id, parent); - } else { - // Case 2: parent is black, left sibling is red and has two black children. - self.rotateRight(parent_id, parent); - } - parent.color = .Red; - sibling.color = .Black; - self.detachFixUp(node_id, parent_id); - return; - } - } - - if (parent.color == .Black and sibling.color == .Black) { - // Case 3: left or right sibling with both black children. - if (s_left_black and s_right_black) { - sibling.color = .Red; - // Recurse at parent. - self.detachFixUp(parent_id, parent.parent); - return; - } - } - - if (parent.color == .Red and sibling.color == .Black) { - // Case 4: left or right sibling with both black children. - if (s_left_black and s_right_black) { - parent.color = .Black; - sibling.color = .Red; - return; - } - } - - if (sibling.color == .Black) { - // Case 5: parent is black, right sibling is black, sibling has red left child and black right child. - if (is_right_sibling and s_left != null and s_left.?.color == .Red and s_right_black) { - self.rotateRight(s_id, sibling); - sibling.color = .Red; - s_left.?.color = .Black; - // Call again to check for case 6. - self.detachFixUp(node_id, parent_id); - return; - } - // Case 5: parent is black, left sibiling is black, sibling has red right child and black left child. - if (!is_right_sibling and s_right != null and s_right.?.color == .Red and s_left_black) { - self.rotateLeft(s_id, sibling); - sibling.color = .Red; - s_right.?.color = .Black; - // Call again to check for case 6. - self.detachFixUp(node_id, parent_id); - return; - } - // Case 6: right sibling with red right child. - if (is_right_sibling and s_right != null and s_right.?.color == .Red) { - self.rotateLeft(parent_id, parent); - sibling.color = parent.color; - parent.color = .Black; - s_right.?.color = .Black; - return; - } - // Case 6: left sibling with red left child. - if (!is_right_sibling and s_left != null and s_left.?.color == .Red) { - self.rotateRight(parent_id, parent); - sibling.color = parent.color; - parent.color = .Black; - s_left.?.color = .Black; - return; - } - } - } - - pub fn insert(self: *Self, val: Value) !Id { - return try self.insertCustom(val, self.ctx, Compare); - } - - /// Duplicate keys are not allowed. - pub fn insertCustom(self: *Self, val: Value, ctx: anytype, cmp: fn (Value, Value, @TypeOf(ctx)) std.math.Order) !Id { - var maybe_id: ?Id = undefined; - var maybe_parent: ?Id = undefined; - var is_left: bool = undefined; - - maybe_id = self.doLookup(val, &maybe_parent, &is_left, ctx, cmp); - if (maybe_id) |_| { - return error.DuplicateKey; - } - - const new_id = try self.buf.add(.{ - .left = NullId, - .right = NullId, - .color = .Red, - .parent = maybe_parent orelse NullId, - .val = val, - }); - self.size += 1; - - if (maybe_parent) |parent| { - self.buf.getPtrNoCheck(parent).setChild(new_id, is_left); - } else { - self.root = new_id; - } - - var node_id = new_id; - while (true) { - var node = self.buf.getPtrNoCheck(node_id); - const parent_id = node.getParentOpt() orelse break; - var parent = self.buf.getPtrNoCheck(parent_id); - if (parent.color == .Black) { - // Current is red, parent is black. Nothing left to do. - break; - } - // If parent is red, there must be a grand parent that is black. - var grandpa_id = parent.getParentOpt() orelse unreachable; - var grandpa = self.buf.getPtrNoCheck(grandpa_id); - - if (parent_id == grandpa.left) { - var opt_psibling = grandpa.right; - const mb_psibling: ?*Node = if (opt_psibling != NullId) self.buf.getPtrNoCheck(opt_psibling) else null; - if (mb_psibling == null or mb_psibling.?.color == .Black) { - // Case #5, parent is red, parent sibling is black, node is inner grandchild. Rotate left first. - if (node_id == parent.right) { - self.rotateLeft(parent_id, parent); - // Make sure reattached right is black since parent is red. - if (parent.right != NullId) { - self.buf.getPtrNoCheck(parent.right).color = .Black; - } - parent = node; - } - parent.color = .Black; - grandpa.color = .Red; - self.rotateRight(grandpa_id, grandpa); - // Make sure reattached left is black since grandpa is red. - if (grandpa.left != NullId) { - self.buf.getPtrNoCheck(grandpa.left).color = .Black; - } - } else { - // parent and parent sibling are both red. Set both to black, and grand parent to red. - parent.color = .Black; - mb_psibling.?.color = .Black; - grandpa.color = .Red; - node_id = grandpa_id; - } - } else { - var opt_psibling = grandpa.left; - const mb_psibling: ?*Node = if (opt_psibling != NullId) self.buf.getPtrNoCheck(opt_psibling) else null; - if (mb_psibling == null or mb_psibling.?.color == .Black) { - // Case #5, parent is red, parent sibling is black, node is inner grandchild. Rotate right first. - if (node_id == parent.left) { - self.rotateRight(parent_id, parent); - // Make sure reattached left is black since parent is red. - if (parent.left != NullId) { - self.buf.getPtrNoCheck(parent.left).color = .Black; - } - parent = node; - } - parent.color = .Black; - grandpa.color = .Red; - self.rotateLeft(grandpa_id, grandpa); - // Make sure reattached right is black since grandpa is red. - if (grandpa.right != NullId) { - self.buf.getPtrNoCheck(grandpa.right).color = .Black; - } - } else { - // parent and parent sibling are both red. Set both to black, and grand parent to red. - parent.color = .Black; - mb_psibling.?.color = .Black; - grandpa.color = .Red; - node_id = grandpa_id; - } - } - } - // This was an insert, there is at least one node. - self.buf.getPtrNoCheck(self.root).color = .Black; - return new_id; - } - - /// lookup searches for the value of key, using binary search. It will - /// return a pointer to the node if it is there, otherwise it will return null. - /// Complexity guaranteed O(log n), where n is the number of nodes book-kept - /// by tree. - pub fn lookup(self: Self, val: Value) ?Id { - var parent: ?Id = undefined; - var is_left: bool = undefined; - return self.doLookup(val, &parent, &is_left, self.ctx, Compare); - } - - /// Lookup with a different compare function. - pub fn lookupCustom(self: Self, val: Value, ctx: anytype, cmp: fn (Value, Value, @TypeOf(ctx)) std.math.Order) ?Id { - var parent: ?Id = undefined; - var is_left: bool = undefined; - return self.doLookup(val, &parent, &is_left, ctx, cmp); - } - - /// Lookup that also sets the parent and is_left to indicate where it was found or where it would be inserted. - pub fn lookupCustomLoc(self: Self, val: Value, ctx: anytype, cmp: fn (Value, Value, @TypeOf(ctx)) std.math.Order, parent: *?Id, is_left: *bool) ?Id { - return self.doLookup(val, parent, is_left, ctx, cmp); - } - - /// If there is a match, the Id is returned. - /// Otherwise, the insert slot is provided by pparent and is_left. - fn doLookup(self: Self, val: Value, pparent: *?Id, is_left: *bool, ctx: anytype, cmp: fn (Value, Value, @TypeOf(ctx)) std.math.Order) ?Id { - var opt_id = self.root; - - pparent.* = null; - is_left.* = false; - - while (opt_id != NullId) { - const node = self.buf.getNoCheck(opt_id); - const res = cmp(val, node.val, ctx); - if (res == .eq) { - return opt_id; - } - pparent.* = opt_id; - switch (res) { - .lt => { - is_left.* = true; - opt_id = node.left; - }, - .gt => { - is_left.* = false; - opt_id = node.right; - }, - .eq => unreachable, // handled above - } - } - return null; - } - - /// e - /// / - /// (a) - /// / \ - /// b c - /// / - /// d - /// Given a, rotate a to b and c to a. a's parent becomes c and right becomes d. c's parent bcomes e and left becomes a. - fn rotateLeft(self: *Self, node_id: Id, node: *Node) void { - if (node.right == NullId) { - unreachable; - } - var right = self.buf.getPtrNoCheck(node.right); - if (!node.isRoot()) { - var parent = self.buf.getPtrNoCheck(node.parent); - if (parent.left == node_id) { - parent.left = node.right; - } else { - parent.right = node.right; - } - right.parent = node.parent; - } else { - self.root = node.right; - right.parent = NullId; - } - node.parent = node.right; - node.right = right.left; - if (node.right != NullId) { - self.buf.getPtrNoCheck(node.right).parent = node_id; - } - right.left = node_id; - } - - /// Works similarily to rotateLeft for the right direction. - fn rotateRight(self: *Self, node_id: Id, node: *Node) void { - if (node.left == NullId) { - unreachable; - } - var left = self.buf.getPtrNoCheck(node.left); - if (!node.isRoot()) { - var parent = self.buf.getPtrNoCheck(node.parent); - if (parent.left == node_id) { - parent.left = node.left; - } else { - parent.right = node.left; - } - left.parent = node.parent; - } else { - self.root = node.left; - left.parent = NullId; - } - node.parent = node.left; - node.left = left.right; - if (node.left != NullId) { - self.buf.getPtrNoCheck(node.left).parent = node_id; - } - left.right = node_id; - } - - pub fn getPrev(self: Self, id: Id) ?Id { - var node = self.buf.getNoCheck(id); - if (node.left != NullId) { - var cur = node.left; - node = self.buf.getNoCheck(cur); - while (node.right != NullId) { - cur = node.right; - node = self.buf.getNoCheck(cur); - } - return cur; - } - var cur = id; - while (true) { - if (node.parent != NullId) { - var p = self.buf.getNoCheck(node.parent); - if (cur != p.left) { - return node.parent; - } - cur = node.parent; - node = p; - } else { - return null; - } - } - } - - pub fn getNext(self: Self, id: Id) ?Id { - var node = self.buf.getNoCheck(id); - if (node.right != NullId) { - var cur = node.right; - node = self.buf.getNoCheck(cur); - while (node.left != NullId) { - cur = node.left; - node = self.buf.getNoCheck(cur); - } - return cur; - } - var cur = id; - while (true) { - if (node.parent != NullId) { - var p = self.buf.getNoCheck(node.parent); - if (cur != p.right) { - return node.parent; - } - cur = node.parent; - node = p; - } else { - return null; - } - } - } - - /// Checks whether the tree is a valid red black tree. - pub fn isValid(self: Self) bool { - if (self.root == NullId) { - return true; - } - const root = self.buf.getNoCheck(self.root); - if (root.color != .Black) { - return false; - } - var black_cnt: u32 = undefined; - return self.isValid2(self.root, &black_cnt); - } - - fn isValid2(self: Self, id: OptId, out_black_cnt: *u32) bool { - if (id == NullId) { - out_black_cnt.* = 1; - return true; - } - const node = self.buf.getNoCheck(id); - if (node.color == .Red) { - // Red node. Both children must be black. - if (node.left != NullId) { - const left = self.buf.getNoCheck(node.left); - if (left.color != .Black) { - return false; - } - } - if (node.right != NullId) { - const right = self.buf.getNoCheck(node.right); - if (right.color != .Black) { - return false; - } - } - } - var left_black_cnt: u32 = undefined; - if (!self.isValid2(node.left, &left_black_cnt)) { - return false; - } - var right_black_cnt: u32 = undefined; - if (!self.isValid2(node.right, &right_black_cnt)) { - return false; - } - if (left_black_cnt != right_black_cnt) { - return false; - } - if (node.color == .Black) { - out_black_cnt.* = left_black_cnt + 1; - } else { - out_black_cnt.* = left_black_cnt; - } - return true; - } - - fn dump(self: Self) void { - self.dump2(self.root); - } - - fn dump2(self: Self, id: Id) void { - if (id == NullId) { - return; - } - const node = self.buf.getNoCheck(id); - log.debug("{}-{} -> {} {}", .{ id, node.color, node.left, node.right }); - self.dump2(node.left); - self.dump2(node.right); - } - - fn insertDetached(self: *Self, val: Value, color: Color) !Id { - return try self.buf.add(.{ - .left = NullId, - .right = NullId, - .color = color, - .parent = NullId, - .val = val, - }); - } - - fn attachLeft(self: *Self, attached_parent: Id, detached: Id) !void { - const parent = self.buf.getPtrNoCheck(attached_parent); - if (parent.left != NullId) { - return error.CantAttach; - } - parent.left = detached; - const left = self.buf.getPtrNoCheck(detached); - left.parent = attached_parent; - self.size += 1; - } - - fn attachRight(self: *Self, attached_parent: Id, detached: Id) !void { - const parent = self.buf.getPtrNoCheck(attached_parent); - if (parent.right != NullId) { - return error.CantAttach; - } - parent.right = detached; - const right = self.buf.getPtrNoCheck(detached); - right.parent = attached_parent; - self.size += 1; - } - }; -} - -fn testCompare(left: u32, right: u32, _: void) std.math.Order { - if (left < right) { - return .lt; - } else if (left == right) { - return .eq; - } else if (left > right) { - return .gt; - } - unreachable; -} - -fn testCompareReverse(left: u32, right: u32, _: void) std.math.Order { - return testCompare(right, left, {}); -} - -test "getNext, getPrev" { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - - try t.expectEqual(tree.getNext(b).?, a); - try t.expectEqual(tree.getNext(a).?, c); - try t.expectEqual(tree.getNext(c), null); - - try t.expectEqual(tree.getPrev(b), null); - try t.expectEqual(tree.getPrev(a).?, b); - try t.expectEqual(tree.getPrev(c).?, a); -} - -test "Insert where rotations need to set reattached nodes to black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insertDetached(9, .Black); - const c = try tree.insertDetached(20, .Red); - const d = try tree.insertDetached(8, .Red); - const e = try tree.insertDetached(19, .Black); - const f = try tree.insertDetached(21, .Black); - const g = try tree.insertDetached(17, .Red); - try tree.attachLeft(a, b); - try tree.attachRight(a, c); - try tree.attachLeft(b, d); - try tree.attachLeft(c, e); - try tree.attachRight(c, f); - try tree.attachLeft(e, g); - try t.expectEqual(tree.isValid(), true); - - const h = try tree.insert(18); - try t.expectEqual(tree.isValid(), true); - - const vals = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(vals); - try t.expectEqualSlices(TestId, vals, &.{ d, b, a, g, h, e, c, f }); -} - -test "Insert case #1: current node parent is black" { - var tree = initTestTree(); - defer tree.deinit(); - - const root = try tree.insert(10); - const node = try tree.insert(9); - - try t.expectEqual(tree.getNode(root).?.color, .Black); - try t.expectEqual(tree.getNode(root).?.left, node); - try t.expectEqual(tree.getNode(node).?.color, .Red); - try t.expectEqual(tree.getNode(node).?.parent, root); -} - -test "Insert case #2/#3/#4: both parent and parent sibling are red" { - var tree = initTestTree(); - defer tree.deinit(); - - const root = try tree.insert(10); - const parent = try tree.insert(9); - const psibling = try tree.insert(11); - const node = try tree.insert(8); - - try t.expectEqual(tree.getNode(root).?.color, .Black); - try t.expectEqual(tree.getNode(root).?.left, parent); - try t.expectEqual(tree.getNode(root).?.right, psibling); - try t.expectEqual(tree.getNode(parent).?.color, .Black); - try t.expectEqual(tree.getNode(parent).?.left, node); - try t.expectEqual(tree.getNode(psibling).?.color, .Black); - try t.expectEqual(tree.getNode(node).?.color, .Red); - try t.expectEqual(tree.getNode(node).?.parent, parent); -} - -test "Insert case #5: parent is red but parent sibling is black, node is inner grandchild" { - var tree = initTestTree(); - defer tree.deinit(); - - const root = try tree.insert(100); - const parent = try tree.insert(50); - _ = try tree.insert(150); - _ = try tree.insert(25); - const node = try tree.insert(75); - _ = try tree.insert(80); - _ = try tree.insert(70); - _ = try tree.insert(60); - - try t.expectEqual(tree.getNode(node).?.parent, TestNullId); - try t.expectEqual(tree.getNode(node).?.color, .Black); - try t.expectEqual(tree.getNode(node).?.right, root); - try t.expectEqual(tree.getNode(node).?.left, parent); - - try t.expectEqual(tree.getNode(root).?.color, .Red); - try t.expectEqual(tree.getNode(root).?.parent, node); - - try t.expectEqual(tree.getNode(parent).?.color, .Red); - try t.expectEqual(tree.getNode(parent).?.parent, node); -} - -test "Insert case #6: parent is red but parent sibling is black, node is outer grandchild" { - var tree = initTestTree(); - defer tree.deinit(); - - const root = try tree.insert(100); - const parent = try tree.insert(50); - _ = try tree.insert(150); - const node = try tree.insert(25); - _ = try tree.insert(70); - _ = try tree.insert(20); - _ = try tree.insert(30); - _ = try tree.insert(15); - - try t.expectEqual(tree.getNode(parent).?.parent, TestNullId); - try t.expectEqual(tree.getNode(parent).?.color, .Black); - try t.expectEqual(tree.getNode(parent).?.right, root); - try t.expectEqual(tree.getNode(parent).?.left, node); - - try t.expectEqual(tree.getNode(root).?.color, .Red); - try t.expectEqual(tree.getNode(root).?.parent, parent); - - try t.expectEqual(tree.getNode(node).?.color, .Red); - try t.expectEqual(tree.getNode(node).?.parent, parent); -} - -test "Insert in order." { - var tree = initTestTree(); - defer tree.deinit(); - - _ = try tree.insert(1); - _ = try tree.insert(2); - _ = try tree.insert(3); - _ = try tree.insert(4); - _ = try tree.insert(5); - _ = try tree.insert(6); - _ = try tree.insert(7); - _ = try tree.insert(8); - _ = try tree.insert(9); - _ = try tree.insert(10); - - const vals = tree.allocValuesInOrder(t.allocator); - defer t.allocator.free(vals); - try t.expectEqualSlices(TestId, vals, &.{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); -} - -test "Insert in reverse order." { - var tree = initTestTree(); - defer tree.deinit(); - - _ = try tree.insert(10); - _ = try tree.insert(9); - _ = try tree.insert(8); - _ = try tree.insert(7); - _ = try tree.insert(6); - _ = try tree.insert(5); - _ = try tree.insert(4); - _ = try tree.insert(3); - _ = try tree.insert(2); - _ = try tree.insert(1); - - const vals = tree.allocValuesInOrder(t.allocator); - defer t.allocator.free(vals); - try t.expectEqualSlices(TestId, vals, &.{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); -} - -test "inserting and looking up" { - var tree = initTestTree(); - defer tree.deinit(); - const orig: u32 = 1000; - const node_id = try tree.insert(orig); - // Assert that identical value finds the same pointer - try t.expectEqual(tree.lookup(1000), node_id); - // Assert that insert duplicate returns error. - try t.expectError(error.DuplicateKey, tree.insert(1000)); - try t.expectEqual(tree.lookup(1000), node_id); - try t.expectEqual(tree.get(node_id).?, orig); - // Assert that if looking for a non-existing value, return null. - try t.expectEqual(tree.lookup(1234), null); -} - -test "multiple inserts, followed by calling first and last" { - // if (@import("builtin").arch == .aarch64) { - // // TODO https://github.com/ziglang/zig/issues/3288 - // return error.SkipZigTest; - // } - var tree = initTestTree(); - defer tree.deinit(); - - _ = try tree.insert(0); - _ = try tree.insert(1); - _ = try tree.insert(2); - const third_id = try tree.insert(3); - try t.expectEqual(tree.get(tree.first().?).?, 0); - try t.expectEqual(tree.get(tree.last().?).?, 3); - try t.expectEqual(tree.lookup(3), third_id); - tree.sort({}, testCompareReverse) catch unreachable; - try t.expectEqual(tree.get(tree.first().?).?, 3); - try t.expectEqual(tree.get(tree.last().?).?, 0); - try t.expectEqual(tree.lookupCustom(3, {}, testCompareReverse), third_id); -} - -test "Remove root with no children." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - try t.expectEqual(tree.root, a); - - try tree.remove(a); - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{}); -} - -test "Remove root with left red child." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(a).?.left, b); - try t.expectEqual(tree.getNode(b).?.color, .Red); - - try tree.remove(a); - try t.expectEqual(tree.getNode(b).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{b}); -} - -test "Remove root with right red child." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(15); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(a).?.right, b); - try t.expectEqual(tree.getNode(b).?.color, .Red); - - try tree.remove(a); - try t.expectEqual(tree.getNode(b).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{b}); -} - -test "Remove root with two red children." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(a).?.left, b); - try t.expectEqual(tree.getNode(a).?.right, c); - try t.expectEqual(tree.getNode(b).?.color, .Red); - try t.expectEqual(tree.getNode(c).?.color, .Red); - - try tree.remove(a); - try t.expectEqual(tree.root, c); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.left, b); - try t.expectEqual(tree.getNode(b).?.color, .Red); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ b, c }); -} - -test "Remove red non-root." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(a).?.left, b); - try t.expectEqual(tree.getNode(b).?.color, .Red); - - try tree.remove(b); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{a}); -} - -test "Remove black non-root with left red child." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(3); - const b = try tree.insert(2); - const c = try tree.insert(4); - const d = try tree.insert(1); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Red); - - try tree.remove(b); - - try t.expectEqual(tree.getNode(d).?.parent, a); - try t.expectEqual(tree.getNode(d).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ d, a, c }); -} - -test "Remove black non-root with right red child." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(3); - const b = try tree.insert(2); - const c = try tree.insert(4); - const d = try tree.insert(5); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Red); - - try tree.remove(c); - - try t.expectEqual(tree.getNode(d).?.parent, a); - try t.expectEqual(tree.getNode(d).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ b, a, d }); -} - -test "Remove non-root with double black case: Parent is red, right sibling is black, and sibling's children are black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(15); - const b = try tree.insert(10); - const c = try tree.insert(20); - const d = try tree.insert(17); - const e = try tree.insert(23); - const f = try tree.insert(25); - try tree.remove(f); - try t.expectEqual(tree.getNode(c).?.color, .Red); - try t.expectEqual(tree.getNode(d).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.left, TestNullId); - try t.expectEqual(tree.getNode(e).?.right, TestNullId); - - try tree.remove(d); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.left, TestNullId); - try t.expectEqual(tree.getNode(e).?.color, .Red); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ b, a, c, e }); -} - -test "Remove non-root with double black case: Parent is red, left sibling is black, and sibling's children are black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(15); - const b = try tree.insert(10); - const c = try tree.insert(20); - const d = try tree.insert(13); - const e = try tree.insert(7); - const f = try tree.insert(5); - try tree.remove(f); - try t.expectEqual(tree.getNode(b).?.color, .Red); - try t.expectEqual(tree.getNode(d).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.left, TestNullId); - try t.expectEqual(tree.getNode(e).?.right, TestNullId); - - try tree.remove(d); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.right, TestNullId); - try t.expectEqual(tree.getNode(e).?.color, .Red); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ e, b, a, c }); -} - -test "Remove non-root with double black case: Right sibling is black, and sibling's right child is red." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - const d = try tree.insert(20); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.right, d); - try t.expectEqual(tree.getNode(d).?.color, .Red); - - try tree.remove(b); - try t.expectEqual(tree.root, c); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ a, c, d }); -} - -test "Remove non-root with double black case: Left sibling is black, and sibling's left child is red." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - const d = try tree.insert(4); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.left, d); - try t.expectEqual(tree.getNode(d).?.color, .Red); - - try tree.remove(c); - try t.expectEqual(tree.root, b); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ d, b, a }); -} - -test "Remove non-root case 5: parent is red, right sibling is black, sibling's left child is red, sibling's right child is black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insertDetached(10, .Red); - const c = try tree.insertDetached(10, .Black); - const d = try tree.insertDetached(10, .Black); - const e = try tree.insertDetached(10, .Black); - const f = try tree.insertDetached(10, .Red); - try tree.attachLeft(a, b); - try tree.attachRight(a, c); - try tree.attachLeft(b, d); - try tree.attachRight(b, e); - try tree.attachLeft(e, f); - try t.expectEqual(tree.isValid(), true); - - t.log_level = .debug; - - try tree.remove(d); - try t.expectEqual(tree.isValid(), true); - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ b, f, e, a, c }); -} - -test "Remove non-root with double black case: Parent is black, right sibling is red, sibling's children are black" { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - const d = try tree.insert(20); - const e = try tree.insert(25); - const f = try tree.insert(30); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Red); - try t.expectEqual(tree.getNode(d).?.left, c); - try t.expectEqual(tree.getNode(d).?.right, e); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.color, .Black); - - try tree.remove(b); - try t.expectEqual(tree.root, d); - try t.expectEqual(tree.getNode(c).?.color, .Red); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ a, c, d, e, f }); -} - -test "Remove non-root with double black case: Parent is black, left sibling is red, sibling's children are black" { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - const d = try tree.insert(4); - const e = try tree.insert(3); - const f = try tree.insert(2); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Red); - try t.expectEqual(tree.getNode(d).?.left, e); - try t.expectEqual(tree.getNode(d).?.right, b); - try t.expectEqual(tree.getNode(e).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Black); - - try tree.remove(c); - try t.expectEqual(tree.root, d); - try t.expectEqual(tree.getNode(b).?.color, .Red); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Black); - try t.expectEqual(tree.getNode(e).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ f, e, d, b, a }); -} - -test "Remove non-root with double black case: Parent is black, right sibling is black, sibling's left child is red, sibling's right child is black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - const d = try tree.insert(13); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.left, d); - try t.expectEqual(tree.getNode(d).?.color, .Red); - - try tree.remove(b); - try t.expectEqual(tree.root, d); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ a, d, c }); -} - -test "Remove non-root with double black case: Parent is black, left sibling is black, sibling's right child is red, sibling's left child is black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(10); - const b = try tree.insert(5); - const c = try tree.insert(15); - const d = try tree.insert(7); - try t.expectEqual(tree.getNode(c).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.right, d); - try t.expectEqual(tree.getNode(d).?.color, .Red); - - try tree.remove(c); - try t.expectEqual(tree.root, d); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(d).?.color, .Black); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ b, d, a }); -} - -test "Remove non-root with double black case: Parent is black, right sibling is black, and sibling's children are black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(15); - const b = try tree.insert(10); - const c = try tree.insert(20); - const d = try tree.insert(5); - try tree.remove(d); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.color, .Black); - - try tree.remove(b); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.color, .Red); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ a, c }); -} - -test "Remove non-root with double black case: Parent is black, left sibling is black, and sibling's children are black." { - var tree = initTestTree(); - defer tree.deinit(); - - const a = try tree.insert(15); - const b = try tree.insert(10); - const c = try tree.insert(20); - const d = try tree.insert(5); - try tree.remove(d); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Black); - try t.expectEqual(tree.getNode(c).?.color, .Black); - - try tree.remove(c); - try t.expectEqual(tree.root, a); - try t.expectEqual(tree.getNode(a).?.color, .Black); - try t.expectEqual(tree.getNode(b).?.color, .Red); - - const node_ids = tree.allocNodeIdsInOrder(t.allocator); - defer t.allocator.free(node_ids); - try t.expectEqualSlices(TestId, node_ids, &.{ b, a }); -} - -fn initTestTree() RbTree(TestId, u32, void, testCompare) { - return RbTree(TestId, u32, void, testCompare).init(t.allocator, {}); -} - -const TestId = u32; -const TestNullId = @import("compact.zig").CompactNull(TestId); diff --git a/examples/gkurve/draw.zig b/examples/gkurve/draw.zig deleted file mode 100644 index 3e933485..00000000 --- a/examples/gkurve/draw.zig +++ /dev/null @@ -1,170 +0,0 @@ -const std = @import("std"); -const ArrayList = std.ArrayList; -const gpu = @import("gpu"); -const App = @import("main.zig").App; -const zm = @import("zmath"); -const UVData = @import("atlas.zig").UVData; - -const Vec2 = @Vector(2, f32); - -pub const Vertex = extern struct { - pos: @Vector(4, f32), - uv: Vec2, -}; -const VERTEX_ATTRIBUTES = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, -}; -pub const VERTEX_BUFFER_LAYOUT = gpu.VertexBufferLayout{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attribute_count = VERTEX_ATTRIBUTES.len, - .attributes = &VERTEX_ATTRIBUTES, -}; -pub const VertexUniform = struct { - mat: zm.Mat, -}; - -const GkurveType = enum(u32) { - concave = 0, - convex = 1, - filled = 2, -}; - -pub const FragUniform = extern struct { - type: GkurveType = .filled, - // Padding for struct alignment to 16 bytes (minimum in WebGPU uniform). - padding: @Vector(3, f32) = undefined, - blend_color: @Vector(4, f32) = @Vector(4, f32){ 1, 1, 1, 1 }, -}; - -pub fn equilateralTriangle(app: *App, position: Vec2, scale: f32, uniform: FragUniform, uv_data: UVData) !void { - const triangle_height = scale * @sqrt(0.75); - - try app.vertices.appendSlice(&[3]Vertex{ - .{ .pos = .{ position[0] + scale / 2, position[1] + triangle_height, 0, 1 }, .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0.5, 1 } }, - .{ .pos = .{ position[0], position[1], 0, 1 }, .uv = uv_data.bottom_left }, - .{ .pos = .{ position[0] + scale, position[1], 0, 1 }, .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 1, 0 } }, - }); - - try app.fragment_uniform_list.append(uniform); - - app.update_vertex_buffer = true; - app.update_frag_uniform_buffer = true; -} - -pub fn quad(app: *App, position: Vec2, scale: Vec2, uniform: FragUniform, uv_data: UVData) !void { - const bottom_right_uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 1, 0 }; - const up_left_uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0, 1 }; - const up_right_uv = uv_data.bottom_left + uv_data.width_and_height; - - try app.vertices.appendSlice(&[6]Vertex{ - .{ .pos = .{ position[0], position[1] + scale[1], 0, 1 }, .uv = up_left_uv }, - .{ .pos = .{ position[0], position[1], 0, 1 }, .uv = uv_data.bottom_left }, - .{ .pos = .{ position[0] + scale[0], position[1], 0, 1 }, .uv = bottom_right_uv }, - - .{ .pos = .{ position[0] + scale[0], position[1] + scale[1], 0, 1 }, .uv = up_right_uv }, - .{ .pos = .{ position[0], position[1] + scale[1], 0, 1 }, .uv = up_left_uv }, - .{ .pos = .{ position[0] + scale[0], position[1], 0, 1 }, .uv = bottom_right_uv }, - }); - - try app.fragment_uniform_list.appendSlice(&.{ uniform, uniform }); - - app.update_vertex_buffer = true; - app.update_frag_uniform_buffer = true; -} - -pub fn circle(app: *App, position: Vec2, radius: f32, blend_color: @Vector(4, f32), uv_data: UVData) !void { - const low_mid = Vertex{ - .pos = .{ position[0], position[1] - radius, 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0.5, 0 }, - }; - const high_mid = Vertex{ - .pos = .{ position[0], position[1] + radius, 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0.5, 1 }, - }; - - const mid_left = Vertex{ - .pos = .{ position[0] - radius, position[1], 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0, 0.5 }, - }; - const mid_right = Vertex{ - .pos = .{ position[0] + radius, position[1], 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 1, 0.5 }, - }; - - const p = 0.95 * radius; - - const high_right = Vertex{ - .pos = .{ position[0] + p, position[1] + p, 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 1, 0.75 }, - }; - const high_left = Vertex{ - .pos = .{ position[0] - p, position[1] + p, 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0, 0.75 }, - }; - const low_right = Vertex{ - .pos = .{ position[0] + p, position[1] - p, 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 1, 0.25 }, - }; - const low_left = Vertex{ - .pos = .{ position[0] - p, position[1] - p, 0, 1 }, - .uv = uv_data.bottom_left + uv_data.width_and_height * Vec2{ 0, 0.25 }, - }; - - try app.vertices.appendSlice(&[_]Vertex{ - low_mid, - mid_right, - high_mid, - - high_mid, - mid_left, - low_mid, - - low_right, - mid_right, - low_mid, - - high_right, - high_mid, - mid_right, - - high_left, - mid_left, - high_mid, - - low_left, - low_mid, - mid_left, - }); - - try app.fragment_uniform_list.appendSlice(&[_]FragUniform{ - .{ - .type = .filled, - .blend_color = blend_color, - }, - .{ - .type = .filled, - .blend_color = blend_color, - }, - .{ - .type = .convex, - .blend_color = blend_color, - }, - .{ - .type = .convex, - .blend_color = blend_color, - }, - .{ - .type = .convex, - .blend_color = blend_color, - }, - .{ - .type = .convex, - .blend_color = blend_color, - }, - }); - - app.update_vertex_buffer = true; - app.update_frag_uniform_buffer = true; -} diff --git a/examples/gkurve/frag.wgsl b/examples/gkurve/frag.wgsl deleted file mode 100755 index 22c66c25..00000000 --- a/examples/gkurve/frag.wgsl +++ /dev/null @@ -1,73 +0,0 @@ -struct FragUniform { - type_: u32, - padding: vec3, - blend_color: vec4, -} -@binding(1) @group(0) var ubos: array; -@binding(2) @group(0) var mySampler: sampler; -@binding(3) @group(0) var myTexture: texture_2d; - -@fragment fn main( - @location(0) uv: vec2, - @interpolate(linear) @location(1) bary: vec2, - @interpolate(flat) @location(2) triangle_index: u32, -) -> @location(0) vec4 { - // Example 1: Visualize barycentric coordinates: - // return vec4(bary.x, bary.y, 0.0, 1.0); - // return vec4(0.0, bary.x, 0.0, 1.0); // [1.0 (bottom-left vertex), 0.0 (bottom-right vertex)] - // return vec4(0.0, bary.y, 0.0, 1.0); // [1.0 (bottom-left vertex), 1.0 (top-right face)] - - // Example 2: Render gkurve primitives - // Concave (inverted quadratic bezier curve) - // inversion = -1.0; - // Convex (inverted quadratic bezier curve) - // inversion = 1.0; - let inversion = select( 1.0, -1.0, ubos[triangle_index].type_ == 1u); - // Texture uvs - // (These two could be cut with vec2(0.0,1.0) + uv * vec2(1.0,-1.0)) - var correct_uv = uv; - correct_uv.y = 1.0 - correct_uv.y; - var color = textureSample(myTexture, mySampler, correct_uv) * ubos[triangle_index].blend_color; - - // Gradients - let px = dpdx(bary.xy); - let py = dpdy(bary.xy); - - // Chain rule - let fx = (2.0 * bary.x) * px.x - px.y; - let fy = (2.0 * bary.x) * py.x - py.y; - - // Signed distance - var dist = (bary.x * bary.x - bary.y) / sqrt(fx * fx + fy * fy); - - dist *= inversion; - dist /= 300.0; - - // Border rendering. - let border_color = vec4(1.0, 0.0, 0.0, 1.0); - let border_width = 3.0; - let border_smoothing = 1.0; - // if (dist > 0.0 && dist <= 0.1) { return vec4(1.0, 0.0, 0.0, 1.0); } - // if (dist > 0.2 && dist <= 0.3) { return vec4(0.0, 0.0, 1.0, 1.0); } - - // // Wireframe rendering. - // let right_face_dist = bary.y; - // let bottom_face_dist = bary.x-bary.y; - // let left_face_dist = 1.0 - ((bottom_face_dist*2.0) + bary.y); - // let normal_bary = vec3(right_face_dist, bottom_face_dist, left_face_dist); - - // let fwd = fwidth(normal_bary); - // let w = smoothstep(border_width * fwd, (border_width + border_smoothing) * fwd, normal_bary); - // let width = 1.0 - min(min(w.x, w.y), w.z); - // let epsilon = 0.001; - // if (right_face_dist >= -epsilon && right_face_dist <= width - // || left_face_dist >= -epsilon && left_face_dist <= width - // || bottom_face_dist >= -epsilon && bottom_face_dist <= width) { - // color = mix(color, border_color, width); - // if (dist < 0.0 && ubos[triangle_index].type_ != 2u) { - // return vec4(border_color.rgb, width); - // } - // } - - return color * f32(dist >= 0.0 || ubos[triangle_index].type_ == 2u); -} diff --git a/examples/gkurve/label.zig b/examples/gkurve/label.zig deleted file mode 100644 index df67406f..00000000 --- a/examples/gkurve/label.zig +++ /dev/null @@ -1,146 +0,0 @@ -//! At the moment we use only rgba32, but maybe it could be useful to use also other types - -const std = @import("std"); -const ft = @import("freetype"); -const zigimg = @import("zigimg"); -const Atlas = @import("atlas.zig").Atlas; -const AtlasErr = @import("atlas.zig").Error; -const UVData = @import("atlas.zig").UVData; -const App = @import("main.zig").App; -const draw = @import("draw.zig"); - -pub const Label = @This(); - -const Vec2 = @Vector(2, f32); -const Vec4 = @Vector(4, f32); - -const GlyphInfo = struct { - uv_data: UVData, - metrics: ft.GlyphMetrics, -}; - -face: ft.Face, -size: i32, -char_map: std.AutoHashMap(u21, GlyphInfo), -allocator: std.mem.Allocator, - -const WriterContext = struct { - label: *Label, - app: *App, - position: Vec2, - text_color: Vec4, -}; -const WriterError = ft.Error || std.mem.Allocator.Error || AtlasErr; -const Writer = std.io.Writer(WriterContext, WriterError, write); - -pub fn writer(label: *Label, app: *App, position: Vec2, text_color: Vec4) Writer { - return Writer{ - .context = .{ - .label = label, - .app = app, - .position = position, - .text_color = text_color, - }, - }; -} - -pub fn init(lib: ft.Library, font_path: []const u8, face_index: i32, char_size: i32, allocator: std.mem.Allocator) !Label { - return Label{ - .face = try lib.createFace(font_path, face_index), - .size = char_size, - .char_map = std.AutoHashMap(u21, GlyphInfo).init(allocator), - .allocator = allocator, - }; -} - -pub fn deinit(label: *Label) void { - label.face.deinit(); - label.char_map.deinit(); -} - -fn write(ctx: WriterContext, bytes: []const u8) WriterError!usize { - var offset = Vec2{ 0, 0 }; - var j: usize = 0; - while (j < bytes.len) { - const len = std.unicode.utf8ByteSequenceLength(bytes[j]) catch unreachable; - const char = std.unicode.utf8Decode(bytes[j..(j + len)]) catch unreachable; - j += len; - switch (char) { - '\n' => { - offset[0] = 0; - offset[1] -= @intToFloat(f32, ctx.label.face.size().metrics().height >> 6); - }, - ' ' => { - const v = try ctx.label.char_map.getOrPut(char); - if (!v.found_existing) { - try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0); - try ctx.label.face.loadChar(char, .{ .render = true }); - const glyph = ctx.label.face.glyph(); - v.value_ptr.* = GlyphInfo{ - .uv_data = undefined, - .metrics = glyph.metrics(), - }; - } - offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6); - }, - else => { - const v = try ctx.label.char_map.getOrPut(char); - if (!v.found_existing) { - try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0); - try ctx.label.face.loadChar(char, .{ .render = true }); - const glyph = ctx.label.face.glyph(); - const glyph_bitmap = glyph.bitmap(); - const glyph_width = glyph_bitmap.width(); - const glyph_height = glyph_bitmap.rows(); - - // Add 1 pixel padding to texture to avoid bleeding over other textures - var glyph_data = try ctx.label.allocator.alloc(zigimg.color.Rgba32, (glyph_width + 2) * (glyph_height + 2)); - defer ctx.label.allocator.free(glyph_data); - const glyph_buffer = glyph_bitmap.buffer().?; - for (glyph_data) |*data, i| { - const x = i % (glyph_width + 2); - const y = i / (glyph_width + 2); - - // zig fmt: off - const glyph_col = - if (x == 0 or x == (glyph_width + 1) or y == 0 or y == (glyph_height + 1)) - 0 - else - glyph_buffer[(y - 1) * glyph_width + (x - 1)]; - // zig fmt: on - - data.* = zigimg.color.Rgba32.initRgb(glyph_col, glyph_col, glyph_col); - } - var glyph_atlas_region = try ctx.app.texture_atlas_data.reserve(ctx.label.allocator, glyph_width + 2, glyph_height + 2); - ctx.app.texture_atlas_data.set(glyph_atlas_region, glyph_data); - - glyph_atlas_region.x += 1; - glyph_atlas_region.y += 1; - glyph_atlas_region.width -= 2; - glyph_atlas_region.height -= 2; - const glyph_uv_data = glyph_atlas_region.getUVData(@intToFloat(f32, ctx.app.texture_atlas_data.size)); - - v.value_ptr.* = GlyphInfo{ - .uv_data = glyph_uv_data, - .metrics = glyph.metrics(), - }; - } - - try draw.quad( - ctx.app, - ctx.position + offset + Vec2{ @intToFloat(f32, v.value_ptr.metrics.horiBearingX >> 6), @intToFloat(f32, (v.value_ptr.metrics.horiBearingY - v.value_ptr.metrics.height) >> 6) }, - .{ @intToFloat(f32, v.value_ptr.metrics.width >> 6), @intToFloat(f32, v.value_ptr.metrics.height >> 6) }, - .{ .blend_color = ctx.text_color }, - v.value_ptr.uv_data, - ); - offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6); - }, - } - } - return bytes.len; -} - -pub fn print(label: *Label, app: *App, comptime fmt: []const u8, args: anytype, position: Vec2, text_color: Vec4) !void { - const w = writer(label, app, position, text_color); - try w.print(fmt, args); -} diff --git a/examples/gkurve/main.zig b/examples/gkurve/main.zig deleted file mode 100644 index 5f1aab0e..00000000 --- a/examples/gkurve/main.zig +++ /dev/null @@ -1,323 +0,0 @@ -// TODO: -// - handle textures better with texture atlas -// - handle adding and removing triangles and quads better - -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const zm = @import("zmath"); -const zigimg = @import("zigimg"); -const glfw = @import("glfw"); -const draw = @import("draw.zig"); -const Atlas = @import("atlas.zig").Atlas; -const ft = @import("freetype"); -const Label = @import("label.zig"); -const ResizableLabel = @import("resizable_label.zig"); - -pub const App = @This(); - -const AtlasRGB8 = Atlas(zigimg.color.Rgba32); - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -vertices: std.ArrayList(draw.Vertex), -update_vertex_buffer: bool, -vertex_uniform_buffer: *gpu.Buffer, -update_vertex_uniform_buffer: bool, -frag_uniform_buffer: *gpu.Buffer, -fragment_uniform_list: std.ArrayList(draw.FragUniform), -update_frag_uniform_buffer: bool, -bind_group: *gpu.BindGroup, -texture_atlas_data: AtlasRGB8, - -pub fn init(app: *App, core: *mach.Core) !void { - const queue = core.device.getQueue(); - - // TODO: Refactor texture atlas size number - app.texture_atlas_data = try AtlasRGB8.init(core.allocator, 1280); - const atlas_size = gpu.Extent3D{ .width = app.texture_atlas_data.size, .height = app.texture_atlas_data.size }; - const atlas_float_size = @intToFloat(f32, app.texture_atlas_data.size); - - const texture = core.device.createTexture(&.{ - .size = atlas_size, - .format = .rgba8_unorm, - .usage = .{ - .texture_binding = true, - .copy_dst = true, - .render_attachment = true, - }, - }); - const data_layout = gpu.Texture.DataLayout{ - .bytes_per_row = @intCast(u32, atlas_size.width * 4), - .rows_per_image = @intCast(u32, atlas_size.height), - }; - - var img = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/gotta-go-fast.png")); - defer img.deinit(); - - const atlas_img_region = try app.texture_atlas_data.reserve(core.allocator, @truncate(u32, img.width), @truncate(u32, img.height)); - const img_uv_data = atlas_img_region.getUVData(atlas_float_size); - - switch (img.pixels) { - .rgba32 => |pixels| app.texture_atlas_data.set(atlas_img_region, pixels), - .rgb24 => |pixels| { - const data = try rgb24ToRgba32(core.allocator, pixels); - defer data.deinit(core.allocator); - app.texture_atlas_data.set(atlas_img_region, data.rgba32); - }, - else => @panic("unsupported image color format"), - } - - const white_tex_scale = 80; - var atlas_white_region = try app.texture_atlas_data.reserve(core.allocator, white_tex_scale, white_tex_scale); - atlas_white_region.x += 1; - atlas_white_region.y += 1; - atlas_white_region.width -= 2; - atlas_white_region.height -= 2; - const white_texture_uv_data = atlas_white_region.getUVData(atlas_float_size); - var white_tex_data = try core.allocator.alloc(zigimg.color.Rgba32, white_tex_scale * white_tex_scale); - defer core.allocator.free(white_tex_data); - std.mem.set(zigimg.color.Rgba32, white_tex_data, zigimg.color.Rgba32.initRgb(0xff, 0xff, 0xff)); - app.texture_atlas_data.set(atlas_white_region, white_tex_data); - - app.vertices = try std.ArrayList(draw.Vertex).initCapacity(core.allocator, 9); - app.fragment_uniform_list = try std.ArrayList(draw.FragUniform).initCapacity(core.allocator, 3); - - // Quick test for using freetype - const lib = try ft.Library.init(); - defer lib.deinit(); - - const size_multiplier = 5; - const character = "è"; - var label = try Label.init(lib, "libs/freetype/upstream/assets/FiraSans-Regular.ttf", 0, 110 * size_multiplier, core.allocator); - defer label.deinit(); - // try label.print(app, "All your game's bases are belong to us èçòà", .{}, @Vector(2, f32){ 0, 420 }, @Vector(4, f32){ 1, 1, 1, 1 }); - try label.print(app, character, .{}, @Vector(2, f32){ 50 * size_multiplier, 40 }, @Vector(4, f32){ 1, 1, 1, 1 }); - - var resizable_label: ResizableLabel = undefined; - try resizable_label.init(lib, "libs/freetype/upstream/assets/FiraSans-Regular.ttf", 0, core.allocator, white_texture_uv_data); - defer resizable_label.deinit(); - try resizable_label.print(app, character, .{}, @Vector(4, f32){ 0, 40, 0, 0 }, @Vector(4, f32){ 1, 1, 1, 1 }, 80 * size_multiplier); - - queue.writeTexture( - &.{ .texture = texture }, - &data_layout, - &.{ .width = app.texture_atlas_data.size, .height = app.texture_atlas_data.size }, - app.texture_atlas_data.data, - ); - - const wsize = core.getWindowSize(); - const window_width = @intToFloat(f32, wsize.width); - const window_height = @intToFloat(f32, wsize.height); - const triangle_scale = 250; - _ = window_width; - _ = window_height; - _ = triangle_scale; - _ = img_uv_data; - // try draw.equilateralTriangle(app, .{ window_width / 2, window_height / 2 }, triangle_scale, .{}, img_uv_data); - // try draw.equilateralTriangle(app, .{ window_width / 2, window_height / 2 - triangle_scale }, triangle_scale, .{ .type = .concave }, img_uv_data); - // try draw.equilateralTriangle(app, .{ window_width / 2 - triangle_scale, window_height / 2 - triangle_scale / 2 }, triangle_scale, .{ .type = .convex }, white_texture_uv_data); - // try draw.quad(app, .{ 0, 0 }, .{ 480, 480 }, .{}, .{ .bottom_left = .{ 0, 0 }, .width_and_height = .{ 1, 1 } }); - // try draw.circle(app, .{ window_width / 2, window_height / 2 }, window_height / 2 - 10, .{ 0, 0.5, 0.75, 1.0 }, white_texture_uv_data); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const blend = gpu.BlendState{ - .color = .{ - .operation = .add, - .src_factor = .src_alpha, - .dst_factor = .one_minus_src_alpha, - }, - .alpha = .{ - .operation = .add, - .src_factor = .one, - .dst_factor = .zero, - }, - }; - - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const vbgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); - const fbgle = gpu.BindGroupLayout.Entry.buffer(1, .{ .fragment = true }, .read_only_storage, true, 0); - const sbgle = gpu.BindGroupLayout.Entry.sampler(2, .{ .fragment = true }, .filtering); - const tbgle = gpu.BindGroupLayout.Entry.texture(3, .{ .fragment = true }, .float, .dimension_2d, false); - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ vbgle, fbgle, sbgle, tbgle }, - }), - ); - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{draw.VERTEX_BUFFER_LAYOUT}, - }), - }; - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .vertex = true }, - .size = @sizeOf(draw.Vertex) * app.vertices.items.len, - .mapped_at_creation = false, - }); - - const vertex_uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(draw.VertexUniform), - .mapped_at_creation = false, - }); - - const frag_uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .storage = true }, - .size = @sizeOf(draw.FragUniform) * app.fragment_uniform_list.items.len, - .mapped_at_creation = false, - }); - - const sampler = core.device.createSampler(&.{ - // .mag_filter = .linear, - // .min_filter = .linear, - }); - - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, vertex_uniform_buffer, 0, @sizeOf(draw.VertexUniform)), - gpu.BindGroup.Entry.buffer(1, frag_uniform_buffer, 0, @sizeOf(draw.FragUniform) * app.vertices.items.len / 3), - gpu.BindGroup.Entry.sampler(2, sampler), - gpu.BindGroup.Entry.textureView(3, texture.createView(&gpu.TextureView.Descriptor{ .dimension = .dimension_2d })), - }, - }), - ); - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = queue; - app.vertex_buffer = vertex_buffer; - app.vertex_uniform_buffer = vertex_uniform_buffer; - app.frag_uniform_buffer = frag_uniform_buffer; - app.bind_group = bind_group; - app.update_vertex_buffer = true; - app.update_vertex_uniform_buffer = true; - app.update_frag_uniform_buffer = true; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); - bgl.release(); -} - -pub fn deinit(app: *App, core: *mach.Core) void { - app.vertex_buffer.release(); - app.vertex_uniform_buffer.release(); - app.frag_uniform_buffer.release(); - app.bind_group.release(); - app.vertices.deinit(); - app.fragment_uniform_list.deinit(); - app.texture_atlas_data.deinit(core.allocator); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - - { - if (app.update_vertex_buffer) { - encoder.writeBuffer(app.vertex_buffer, 0, app.vertices.items); - app.update_vertex_buffer = false; - } - if (app.update_frag_uniform_buffer) { - encoder.writeBuffer(app.frag_uniform_buffer, 0, app.fragment_uniform_list.items); - app.update_frag_uniform_buffer = false; - } - if (app.update_vertex_uniform_buffer) { - encoder.writeBuffer(app.vertex_uniform_buffer, 0, &[_]draw.VertexUniform{try getVertexUniformBufferObject(core)}); - app.update_vertex_uniform_buffer = false; - } - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(draw.Vertex) * app.vertices.items.len); - pass.setBindGroup(0, app.bind_group, &.{ 0, 0 }); - pass.draw(@truncate(u32, app.vertices.items.len), 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} - -pub fn resize(app: *App, _: *mach.Core, _: u32, _: u32) !void { - app.update_vertex_uniform_buffer = true; -} - -fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage { - const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len); - var i: usize = 0; - while (i < in.len) : (i += 1) { - out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 }; - } - return out; -} - -// Move to draw.zig -pub fn getVertexUniformBufferObject(core: *mach.Core) !draw.VertexUniform { - // Note: We use window width/height here, not framebuffer width/height. - // On e.g. macOS, window size may be 640x480 while framebuffer size may be - // 1280x960 (subpixels.) Doing this lets us use a pixel, not subpixel, - // coordinate system. - const window_size = core.getWindowSize(); - const proj = zm.orthographicRh( - @intToFloat(f32, window_size.width), - @intToFloat(f32, window_size.height), - -100, - 100, - ); - - const mvp = zm.mul(proj, zm.translation(-1, -1, 0)); - return draw.VertexUniform{ - .mat = mvp, - }; -} diff --git a/examples/gkurve/resizable_label.zig b/examples/gkurve/resizable_label.zig deleted file mode 100644 index 30d4a588..00000000 --- a/examples/gkurve/resizable_label.zig +++ /dev/null @@ -1,453 +0,0 @@ -//! TODO: Refactor the API, maybe use a handle that contains the lib and other things and controls init and deinit of ft.Lib and other things - -const std = @import("std"); -const ft = @import("freetype"); -const zigimg = @import("zigimg"); -const Atlas = @import("atlas.zig").Atlas; -const AtlasErr = @import("atlas.zig").Error; -const UVData = @import("atlas.zig").UVData; -const App = @import("main.zig").App; -const draw = @import("draw.zig"); -const Vertex = draw.Vertex; -const Tessellator = @import("tessellator.zig").Tessellator; - -// If true, show the filled triangles green, the concave beziers blue and the convex ones red -const debug_colors = false; - -pub const ResizableLabel = @This(); - -const Vec2 = @Vector(2, f32); -const Vec4 = @Vector(4, f32); -const VertexList = std.ArrayList(Vertex); - -// All the data that a single character needs to be rendered -// TODO: hori/vert advance, write file format -const CharVertices = struct { - filled_vertices: VertexList, - filled_vertices_indices: std.ArrayList(u16), - // Concave vertices belong to the filled_vertices list, so just index them - concave_vertices: std.ArrayList(u16), - // The point outside of the convex bezier, doesn't belong to the filled vertices, - // But the other two points do, so put those in the indices - convex_vertices: VertexList, - convex_vertices_indices: std.ArrayList(u16), - - fn deinit(self: CharVertices) void { - self.filled_vertices.deinit(); - self.filled_vertices_indices.deinit(); - self.concave_vertices.deinit(); - self.convex_vertices.deinit(); - self.convex_vertices_indices.deinit(); - } -}; - -face: ft.Face, -char_map: std.AutoHashMap(u21, CharVertices), -allocator: std.mem.Allocator, -tessellator: Tessellator, -white_texture: UVData, - -// The data that the write function needs -// TODO: move twxture here, don't limit to just white_texture -const WriterContext = struct { - label: *ResizableLabel, - app: *App, - position: Vec4, - text_color: Vec4, - text_size: u32, -}; -const WriterError = ft.Error || std.mem.Allocator.Error || AtlasErr; -const Writer = std.io.Writer(WriterContext, WriterError, write); - -pub fn writer(label: *ResizableLabel, app: *App, position: Vec4, text_color: Vec4, text_size: u32) Writer { - return Writer{ - .context = .{ - .label = label, - .app = app, - .position = position, - .text_color = text_color, - .text_size = text_size, - }, - }; -} - -pub fn init(self: *ResizableLabel, lib: ft.Library, font_path: []const u8, face_index: i32, allocator: std.mem.Allocator, white_texture: UVData) !void { - self.* = ResizableLabel{ - .face = try lib.createFace(font_path, face_index), - .char_map = std.AutoHashMap(u21, CharVertices).init(allocator), - .allocator = allocator, - .tessellator = undefined, - .white_texture = white_texture, - }; - self.tessellator.init(self.allocator); -} - -pub fn deinit(label: *ResizableLabel) void { - label.face.deinit(); - label.tessellator.deinit(); - - var iter = label.char_map.valueIterator(); - while (iter.next()) |ptr| { - ptr.deinit(); - } - - label.char_map.deinit(); -} - -// TODO: handle offsets -// FIXME: many useless allocations for the arraylists -fn write(ctx: WriterContext, bytes: []const u8) WriterError!usize { - var offset = Vec4{ 0, 0, 0, 0 }; - var c: usize = 0; - while (c < bytes.len) { - const len = std.unicode.utf8ByteSequenceLength(bytes[c]) catch unreachable; - const char = std.unicode.utf8Decode(bytes[c..(c + len)]) catch unreachable; - c += len; - switch (char) { - '\n' => { - offset[0] = 0; - offset[1] -= @intToFloat(f32, ctx.label.face.size().metrics().height >> 6); - std.debug.todo("New line not implemented yet"); - }, - ' ' => { - std.debug.todo("Space character not implemented yet"); - // const v = try ctx.label.char_map.getOrPut(char); - // if (!v.found_existing) { - // try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0); - // try ctx.label.face.loadChar(char, .{ .render = true }); - // const glyph = ctx.label.face.glyph; - // v.value_ptr.* = GlyphInfo{ - // .uv_data = undefined, - // .metrics = glyph.metrics(), - // }; - // } - // offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6); - }, - else => { - const v = try ctx.label.char_map.getOrPut(char); - if (!v.found_existing) { - try ctx.label.face.loadChar(char, .{ .no_scale = true, .no_bitmap = true }); - const glyph = ctx.label.face.glyph(); - - // Use a big scale and then scale to the actual text size - const multiplier = 1024 << 6; - const matrix = ft.Matrix{ - .xx = 1 * multiplier, - .xy = 0 * multiplier, - .yx = 0 * multiplier, - .yy = 1 * multiplier, - }; - glyph.outline().?.transform(matrix); - - v.value_ptr.* = CharVertices{ - .filled_vertices = VertexList.init(ctx.label.allocator), - .filled_vertices_indices = std.ArrayList(u16).init(ctx.label.allocator), - .concave_vertices = std.ArrayList(u16).init(ctx.label.allocator), - .convex_vertices = VertexList.init(ctx.label.allocator), - .convex_vertices_indices = std.ArrayList(u16).init(ctx.label.allocator), - }; - - var outline_ctx = OutlineContext{ - .outline_verts = std.ArrayList(std.ArrayList(Vec2)).init(ctx.label.allocator), - .inside_verts = std.ArrayList(Vec2).init(ctx.label.allocator), - .concave_vertices = std.ArrayList(Vec2).init(ctx.label.allocator), - .convex_vertices = std.ArrayList(Vec2).init(ctx.label.allocator), - }; - defer outline_ctx.outline_verts.deinit(); - defer { - for (outline_ctx.outline_verts.items) |*item| { - item.deinit(); - } - } - defer outline_ctx.inside_verts.deinit(); - defer outline_ctx.concave_vertices.deinit(); - defer outline_ctx.convex_vertices.deinit(); - - const callbacks = ft.Outline.Funcs(*OutlineContext){ - .move_to = moveToFunction, - .line_to = lineToFunction, - .conic_to = conicToFunction, - .cubic_to = cubicToFunction, - .shift = 0, - .delta = 0, - }; - try ctx.label.face.glyph().outline().?.decompose(&outline_ctx, callbacks); - - uniteOutsideAndInsideVertices(&outline_ctx); - - // Tessellator.triangulatePolygons() doesn't seem to work, so just - // call triangulatePolygon() for each polygon, and put the results all - // in all_outlines and all_indices - var all_outlines = std.ArrayList(Vec2).init(ctx.label.allocator); - defer all_outlines.deinit(); - var all_indices = std.ArrayList(u16).init(ctx.label.allocator); - defer all_indices.deinit(); - var idx_offset: u16 = 0; - for (outline_ctx.outline_verts.items) |item| { - ctx.label.tessellator.triangulatePolygon(item.items); - defer ctx.label.tessellator.clearBuffers(); - try all_outlines.appendSlice(ctx.label.tessellator.out_verts.items); - for (ctx.label.tessellator.out_idxes.items) |idx| { - try all_indices.append(idx + idx_offset); - } - idx_offset += @intCast(u16, ctx.label.tessellator.out_verts.items.len); - } - - for (all_outlines.items) |item| { - // FIXME: The uv_data is wrong, should be pushed up by the lowest a character can be - const vertex_uv = item / @splat(2, @as(f32, 1024 << 6)); - const vertex_pos = Vec4{ item[0], item[1], 0, 1 }; - try v.value_ptr.filled_vertices.append(Vertex{ .pos = vertex_pos, .uv = vertex_uv }); - } - try v.value_ptr.filled_vertices_indices.appendSlice(all_indices.items); - - // FIXME: instead of finding the closest vertex and use its index maybe use indices directly in the moveTo,... functions - var i: usize = 0; - while (i < outline_ctx.concave_vertices.items.len) : (i += 1) { - for (all_outlines.items) |item, j| { - const dist = @reduce(.Add, (item - outline_ctx.concave_vertices.items[i]) * (item - outline_ctx.concave_vertices.items[i])); - if (dist < 0.1) { - try v.value_ptr.concave_vertices.append(@truncate(u16, j)); - break; - } - } - } - - i = 0; - while (i < outline_ctx.convex_vertices.items.len) : (i += 3) { - const vert = outline_ctx.convex_vertices.items[i]; - const vertex_uv = vert / @splat(2, @as(f32, 1024 << 6)); - const vertex_pos = Vec4{ vert[0], vert[1], 0, 1 }; - try v.value_ptr.convex_vertices.append(Vertex{ .pos = vertex_pos, .uv = vertex_uv }); - - for (all_outlines.items) |item, j| { - const dist1 = @reduce(.Add, (item - outline_ctx.convex_vertices.items[i + 1]) * (item - outline_ctx.convex_vertices.items[i + 1])); - if (dist1 < 0.1) { - try v.value_ptr.convex_vertices_indices.append(@truncate(u16, j)); - } - - const dist2 = @reduce(.Add, (item - outline_ctx.convex_vertices.items[i + 2]) * (item - outline_ctx.convex_vertices.items[i + 2])); - if (dist2 < 0.1) { - try v.value_ptr.convex_vertices_indices.append(@truncate(u16, j)); - } - } - } - - ctx.label.tessellator.clearBuffers(); - } - - // Read the data and apply resizing of pos and uv - var filled_vertices_after_offset = try ctx.label.allocator.alloc(Vertex, v.value_ptr.filled_vertices.items.len); - defer ctx.label.allocator.free(filled_vertices_after_offset); - for (filled_vertices_after_offset) |*vert, i| { - vert.* = v.value_ptr.filled_vertices.items[i]; - vert.pos *= Vec4{ @intToFloat(f32, ctx.text_size) / 1024, @intToFloat(f32, ctx.text_size) / 1024, 0, 1 }; - vert.pos += ctx.position + offset; - vert.uv = vert.uv * ctx.label.white_texture.width_and_height + ctx.label.white_texture.bottom_left; - } - - var actual_filled_vertices_to_use = try ctx.label.allocator.alloc(Vertex, v.value_ptr.filled_vertices_indices.items.len); - defer ctx.label.allocator.free(actual_filled_vertices_to_use); - for (actual_filled_vertices_to_use) |*vert, i| { - vert.* = filled_vertices_after_offset[v.value_ptr.filled_vertices_indices.items[i]]; - } - try ctx.app.vertices.appendSlice(actual_filled_vertices_to_use); - - if (debug_colors) { - try ctx.app.fragment_uniform_list.appendNTimes(.{ .blend_color = .{ 0, 1, 0, 1 } }, actual_filled_vertices_to_use.len / 3); - } else { - try ctx.app.fragment_uniform_list.appendNTimes(.{ .blend_color = ctx.text_color }, actual_filled_vertices_to_use.len / 3); - } - - var convex_vertices_after_offset = try ctx.label.allocator.alloc(Vertex, v.value_ptr.convex_vertices.items.len + v.value_ptr.convex_vertices_indices.items.len); - defer ctx.label.allocator.free(convex_vertices_after_offset); - var j: u16 = 0; - var k: u16 = 0; - while (j < convex_vertices_after_offset.len) : (j += 3) { - convex_vertices_after_offset[j] = v.value_ptr.convex_vertices.items[j / 3]; - convex_vertices_after_offset[j].pos *= Vec4{ @intToFloat(f32, ctx.text_size) / 1024, @intToFloat(f32, ctx.text_size) / 1024, 0, 1 }; - convex_vertices_after_offset[j].pos += ctx.position + offset; - convex_vertices_after_offset[j].uv = convex_vertices_after_offset[j].uv * ctx.label.white_texture.width_and_height + ctx.label.white_texture.bottom_left; - - convex_vertices_after_offset[j + 1] = filled_vertices_after_offset[v.value_ptr.convex_vertices_indices.items[k]]; - convex_vertices_after_offset[j + 2] = filled_vertices_after_offset[v.value_ptr.convex_vertices_indices.items[k + 1]]; - k += 2; - } - try ctx.app.vertices.appendSlice(convex_vertices_after_offset); - - if (debug_colors) { - try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .convex, .blend_color = .{ 1, 0, 0, 1 } }, convex_vertices_after_offset.len / 3); - } else { - try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .convex, .blend_color = ctx.text_color }, convex_vertices_after_offset.len / 3); - } - - var concave_vertices_after_offset = try ctx.label.allocator.alloc(Vertex, v.value_ptr.concave_vertices.items.len); - defer ctx.label.allocator.free(concave_vertices_after_offset); - for (concave_vertices_after_offset) |*vert, i| { - vert.* = filled_vertices_after_offset[v.value_ptr.concave_vertices.items[i]]; - } - try ctx.app.vertices.appendSlice(concave_vertices_after_offset); - - if (debug_colors) { - try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .concave, .blend_color = .{ 0, 0, 1, 1 } }, concave_vertices_after_offset.len / 3); - } else { - try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .concave, .blend_color = ctx.text_color }, concave_vertices_after_offset.len / 3); - } - - ctx.app.update_vertex_buffer = true; - ctx.app.update_frag_uniform_buffer = true; - - // offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6); - }, - } - } - return bytes.len; -} - -// First move to initialize the outline, (first point), -// After many Q L or C, we will come back to the first point and then call M again if we need to hollow -// On the second M, we instead use an L to connect the first point to the start of the hollow path. -// We then follow like normal and at the end of the hollow path we use another L to close the path. - -// This is basically how an o would be drawn, each ┌... character is a Vertex -// ┌--------┐ -// | | -// | | -// | | -// | ┌----┐ | -// └-┘ | | Consider the vertices here and below to be at the same height, they are coincident -// ┌-┐ | | -// | └----┘ | -// | | -// | | -// | | -// └--------┘ - -const OutlineContext = struct { - // There may be more than one polygon (for example with 'i' we have the polygon of the base and another for the circle) - outline_verts: std.ArrayList(std.ArrayList(Vec2)), - - // The internal outline, used for carving the shape (for example in a, we would first get the outline of the a, but if we stopped there, it woul - // be filled, so we need another outline for carving the filled polygon) - inside_verts: std.ArrayList(Vec2), - - // For the concave and convex beziers - concave_vertices: std.ArrayList(Vec2), - convex_vertices: std.ArrayList(Vec2), -}; - -// If there are elements in inside_verts, unite them with the outline_verts, effectively carving the shape -fn uniteOutsideAndInsideVertices(ctx: *OutlineContext) void { - if (ctx.inside_verts.items.len != 0) { - // Check which point of outline is closer to the first of inside - var last_outline = &ctx.outline_verts.items[ctx.outline_verts.items.len - 1]; - const closest_to_inside: usize = blk: { - const first_point_inside = ctx.inside_verts.items[0]; - var min: f32 = std.math.f32_max; - var closest_index: usize = undefined; - - for (last_outline.items) |item, i| { - const dist = @reduce(.Add, (item - first_point_inside) * (item - first_point_inside)); - if (dist < min) { - min = dist; - closest_index = i; - } - } - break :blk closest_index; - }; - - ctx.inside_verts.append(last_outline.items[closest_to_inside]) catch unreachable; - last_outline.insertSlice(closest_to_inside + 1, ctx.inside_verts.items) catch unreachable; - ctx.inside_verts.clearRetainingCapacity(); - } -} -// TODO: Return also allocation error -fn moveToFunction(ctx: *OutlineContext, _to: ft.Vector) ft.Error!void { - uniteOutsideAndInsideVertices(ctx); - - const to = Vec2{ @intToFloat(f32, _to.x), @intToFloat(f32, _to.y) }; - - // To check wether a point is carving a polygon, - // Cast a ray to the right of the point and check - // when this ray intersects the edges of the polygons, - // if the number of intersections is odd -> inside, - // if it's even -> outside - var new_point_is_inside = false; - for (ctx.outline_verts.items) |polygon| { - var i: usize = 1; - while (i < polygon.items.len) : (i += 1) { - const v1 = polygon.items[i - 1]; - const v2 = polygon.items[i]; - - const min_y = @minimum(v1[1], v2[1]); - const max_y = @maximum(v1[1], v2[1]); - const min_x = @minimum(v1[0], v2[0]); - - // If the point is at the same y as another, it may be counted twice, - // That's why we add the last != - if (to[1] >= min_y and to[1] <= max_y and to[0] >= min_x and to[1] != v2[1]) { - new_point_is_inside = !new_point_is_inside; - } - } - } - - // If the point is inside, put it in the inside verts - if (new_point_is_inside) { - ctx.inside_verts.append(to) catch unreachable; - } else { - // Otherwise create a new polygon - var new_outline_list = std.ArrayList(Vec2).init(ctx.outline_verts.allocator); - new_outline_list.append(to) catch unreachable; - ctx.outline_verts.append(new_outline_list) catch unreachable; - } -} - -fn lineToFunction(ctx: *OutlineContext, to: ft.Vector) ft.Error!void { - // std.log.info("L {} {}", .{ to.x, to.y }); - - // If inside_verts is not empty, we need to fill it - if (ctx.inside_verts.items.len != 0) { - ctx.inside_verts.append(.{ @intToFloat(f32, to.x), @intToFloat(f32, to.y) }) catch unreachable; - } else { - // Otherwise append the new point to the last polygon - ctx.outline_verts.items[ctx.outline_verts.items.len - 1].append(.{ @intToFloat(f32, to.x), @intToFloat(f32, to.y) }) catch unreachable; - } -} - -fn conicToFunction(ctx: *OutlineContext, _control: ft.Vector, _to: ft.Vector) ft.Error!void { - // std.log.info("C {} {} {} {}", .{ control.x, control.y, to.x, to.y }); - const control = Vec2{ @intToFloat(f32, _control.x), @intToFloat(f32, _control.y) }; - const to = Vec2{ @intToFloat(f32, _to.x), @intToFloat(f32, _to.y) }; - - // Either the inside verts or the outine ones - var verts_to_write = if (ctx.inside_verts.items.len != 0) &ctx.inside_verts else &ctx.outline_verts.items[ctx.outline_verts.items.len - 1]; - const previous_point = verts_to_write.items[verts_to_write.items.len - 1]; - - const vertices = [_]Vec2{ control, to, previous_point }; - - const vec1 = control - previous_point; - const vec2 = to - control; - - // if ccw, it's concave, else it's convex - if ((vec1[0] * vec2[1] - vec1[1] * vec2[0]) > 0) { - ctx.concave_vertices.appendSlice(&vertices) catch unreachable; - verts_to_write.append(control) catch unreachable; - } else { - ctx.convex_vertices.appendSlice(&vertices) catch unreachable; - } - verts_to_write.append(to) catch unreachable; -} - -// Doesn't seem to be used much -fn cubicToFunction(ctx: *OutlineContext, control_0: ft.Vector, control_1: ft.Vector, to: ft.Vector) ft.Error!void { - _ = ctx; - _ = control_0; - _ = control_1; - _ = to; - @panic("TODO: search how to approximate cubic bezier with quadratic ones"); -} - -pub fn print(label: *ResizableLabel, app: *App, comptime fmt: []const u8, args: anytype, position: Vec4, text_color: Vec4, text_size: u32) !void { - const w = writer(label, app, position, text_color, text_size); - try w.print(fmt, args); -} diff --git a/examples/gkurve/tessellator.zig b/examples/gkurve/tessellator.zig deleted file mode 100644 index 3293e3dc..00000000 --- a/examples/gkurve/tessellator.zig +++ /dev/null @@ -1,1857 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const t = std.testing; -const Vec2 = @Vector(2, f32); -const zm = @import("zmath"); -const RbTree = @import("data_structures/rb_tree.zig").RbTree; - -inline fn cross(v1: Vec2, v2: Vec2) f32 { - return v1[0] * v2[1] - v1[1] * v2[0]; -} - -pub fn dassert(pred: bool) void { - if (builtin.mode == .Debug) { - std.debug.assert(pred); - } -} -const CompactSinglyLinkedListBuffer = @import("data_structures/compact.zig").CompactSinglyLinkedListBuffer; - -const trace = @import("tracy.zig").trace; - -const log_ = std.log.scoped(.tessellator); - -const DeferredVertexNodeId = u16; -const NullId = @import("data_structures/compact.zig").CompactNull(DeferredVertexNodeId); - -const EventQueue = std.PriorityQueue(u32, *std.ArrayList(Event), compareEventIdx); - -const debug = false and builtin.mode == .Debug; - -pub fn log(comptime format: []const u8, args: anytype) void { - if (debug) { - log_.debug(format, args); - } -} - -pub const Tessellator = struct { - /// Buffers. - verts: std.ArrayList(InternalVertex), - events: std.ArrayList(Event), - event_q: EventQueue, - sweep_edges: RbTree(u16, SweepEdge, Event, compareSweepEdge), - deferred_verts: CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode), - - /// Verts to output. - /// No duplicate verts will be outputed to reduce size footprint. - /// Since some verts won't be discovered until during the processing of events (edge intersections), - /// verts are added as the events are processed. As a result, the verts are also in order y-asc and x-asc. - out_verts: std.ArrayList(Vec2), - - /// Triangles to output are triplets of indexes that point to verts. They are in ccw direction. - out_idxes: std.ArrayList(u16), - - cur_x: f32, - cur_y: f32, - cur_out_vert_idx: u16, - cur_polys: []const []const Vec2, - - const Self = @This(); - - pub fn init(self: *Self, alloc: std.mem.Allocator) void { - self.* = .{ - .verts = std.ArrayList(InternalVertex).init(alloc), - .events = std.ArrayList(Event).init(alloc), - .event_q = undefined, - .sweep_edges = RbTree(u16, SweepEdge, Event, compareSweepEdge).init(alloc, undefined), - .deferred_verts = CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode).init(alloc), - .out_verts = std.ArrayList(Vec2).init(alloc), - .out_idxes = std.ArrayList(u16).init(alloc), - .cur_x = undefined, - .cur_y = undefined, - .cur_out_vert_idx = undefined, - .cur_polys = undefined, - }; - self.event_q = EventQueue.init(alloc, &self.events); - } - - pub fn deinit(self: *Self) void { - self.verts.deinit(); - self.events.deinit(); - self.event_q.deinit(); - self.sweep_edges.deinit(); - self.deferred_verts.deinit(); - self.out_verts.deinit(); - self.out_idxes.deinit(); - } - - pub fn clearBuffers(self: *Self) void { - self.verts.clearRetainingCapacity(); - self.events.clearRetainingCapacity(); - self.event_q.len = 0; - self.sweep_edges.clearRetainingCapacity(); - self.deferred_verts.clearRetainingCapacity(); - self.out_verts.clearRetainingCapacity(); - self.out_idxes.clearRetainingCapacity(); - } - - pub fn triangulatePolygon(self: *Self, polygon: []const Vec2) void { - self.triangulatePolygons(&.{polygon}); - } - - /// Perform a plane sweep to triangulate a complex polygon in one pass. - /// The output returns ccw triangle vertices and indexes ready to be fed into the gpu. - /// This uses Bentley-Ottmann to handle self intersecting edges. - /// Rules are followed to partition into y-monotone polygons and triangulate them. - /// This is ported from the JS implementation (tessellator.js) where it is easier to prototype. - /// Since the number of verts and indexes is not known beforehand, the output is an ArrayList. - /// TODO: See if inline callbacks would be faster to directly push data to the batcher buffer. - pub fn triangulatePolygons(self: *Self, polygons: []const []const Vec2) void { - // Construct the initial events by traversing the polygon. - self.initEvents(polygons); - - self.cur_x = std.math.f32_min; - self.cur_y = std.math.f32_min; - self.cur_out_vert_idx = std.math.maxInt(u16); - self.cur_polys = polygons; - - // Process events. - while (self.event_q.removeOrNull()) |e_id| { - self.processEvent(e_id); - } - } - - /// Initializes the events only. - pub fn debugTriangulatePolygons(self: *Self, polygons: []const []const Vec2) void { - self.initEvents(polygons); - self.cur_x = std.math.f32_min; - self.cur_y = std.math.f32_min; - self.cur_out_vert_idx = std.math.maxInt(u16); - self.cur_polys = polygons; - } - - /// Process the next event. This can be used with debugTriangulatePolygons. - pub fn debugProcessNext(self: *Self, alloc: std.mem.Allocator) ?DebugTriangulateStepResult { - const e_id = self.event_q.removeOrNull() orelse return null; - self.processEvent(e_id); - - return DebugTriangulateStepResult{ - .has_result = true, - .event = self.events.items[e_id], - .sweep_edges = self.sweep_edges.allocValuesInOrder(alloc), - .out_verts = alloc.dupe(Vec2, self.out_verts.items) catch unreachable, - .out_idxes = alloc.dupe(u16, self.out_idxes.items) catch unreachable, - .verts = alloc.dupe(InternalVertex, self.verts.items) catch unreachable, - .deferred_verts = alloc.dupe(CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode).Node, self.deferred_verts.nodes.data.items) catch unreachable, - }; - } - - fn processEvent(self: *Self, e_id: u32) void { - const t_ = trace(@src()); - defer t_.end(); - const sweep_edges = &self.sweep_edges; - - const e = self.events.items[e_id]; - - // If the point changed, allocate a new out vertex index. - if (e.vert_x != self.cur_x or e.vert_y != self.cur_y) { - self.out_verts.append(Vec2{ e.vert_x, e.vert_y }) catch unreachable; - self.cur_out_vert_idx +%= 1; - self.cur_x = e.vert_x; - self.cur_y = e.vert_y; - } - - // Set the out vertex index on the event. - self.verts.items[e.vert_idx].out_idx = self.cur_out_vert_idx; - - if (debug) { - const tag_str: []const u8 = if (e.tag == .Start) "Start" else "End"; - log("--process event {}, ({},{}) {s} {s}", .{ e.vert_idx, e.vert_x, e.vert_y, tag_str, edgeToString(e.edge) }); - log("sweep edges: ", .{}); - var mb_cur = sweep_edges.first(); - while (mb_cur) |cur| { - const se = sweep_edges.get(cur).?; - log("{} -> {}", .{ se.edge.start_idx, se.edge.end_idx }); - mb_cur = sweep_edges.getNext(cur); - } - } - - if (e.tag == .Start) { - // Check to remove a previous left edge and continue it's sub-polygon vertex queue. - sweep_edges.ctx = e; - const new_id = sweep_edges.insert(SweepEdge.init(e, self.verts.items)) catch unreachable; - const new = sweep_edges.getPtr(new_id).?; - - log("start new sweep edge {}", .{new_id}); - - var mb_left_id = sweep_edges.getPrev(new_id); - // Update winding based on what is to the left. - if (mb_left_id == null) { - new.interior_is_left = false; - } else { - new.interior_is_left = !sweep_edges.get(mb_left_id.?).?.interior_is_left; - } - - if (new.interior_is_left) { - log("initially interior to the left", .{}); - // Initially appears to be a right edge but if it connects to the previous left, it becomes a left edge. - var left = sweep_edges.getPtrNoCheck(mb_left_id.?); - const e_vert_out_idx = self.verts.items[e.vert_idx].out_idx; - if (left.end_event_vert_uniq_idx == e_vert_out_idx) { - // Remove the previous ended edge, and takes it's place as the left edge. - defer sweep_edges.remove(mb_left_id.?) catch unreachable; - new.interior_is_left = false; - log("continuation from prev edge, interior to the right", .{}); - - // Previous left edge and this new edge forms a regular left angle. - // Check for bad up cusp. - if (left.bad_up_cusp_uniq_idx != NullId) { - // This monotone polygon (a) should already have run it's triangulate steps from the left edge's end event. - // \ / - // a \/ b - // ^ Bad cusp. - // \_ - // ^ End event from left edge happened before, currently processing start event for the new connected edge. - - // A line is connected from this vertex to the bad cusp to ensure that polygon (a) is monotone and polygon (b) is monotone. - // Since polygon (a) already ran it's triangulate step, it's done from this side of the polygon. - // This start event will create a new sweep edge, so transfer the deferred queue from the bad cusp's right side. (it is now the queue for this new left edge). - const bad_right = sweep_edges.getNoCheck(left.bad_up_cusp_right_sweep_edge_id); - bad_right.dumpQueue(self); - new.deferred_queue = bad_right.deferred_queue; - new.deferred_queue_size = bad_right.deferred_queue_size; - new.cur_side = bad_right.cur_side; - - // Also run triangulate on polygon (b) for the new vertex since the end event was already run for polygon (a). - self.triangulateLeftStep(new, self.verts.items[e.vert_idx]); - - left.bad_up_cusp_uniq_idx = NullId; - sweep_edges.removeDetached(left.bad_up_cusp_right_sweep_edge_id); - - log("FIX BAD UP CUSP", .{}); - new.dumpQueue(self); - } else { - new.deferred_queue = left.deferred_queue; - new.deferred_queue_size = left.deferred_queue_size; - new.cur_side = left.cur_side; - } - } else if (left.start_event_vert_uniq_idx == e_vert_out_idx) { - // Down cusp. - log("DOWN CUSP", .{}); - } - } else { - log("initially interior to the right", .{}); - // Initially appears to be a left edge but if it connects to a previous right, it becomes a right edge. - if (mb_left_id != null) { - const vert = self.verts.items[e.vert_idx]; - - // left edge has interior to the left. - var left = sweep_edges.get(mb_left_id.?).?; - if (left.end_event_vert_uniq_idx == vert.out_idx) { - // Remove previous ended edge. - sweep_edges.remove(mb_left_id.?) catch unreachable; - new.interior_is_left = true; - log("changed to interior is left", .{}); - } else if (left.start_event_vert_uniq_idx == vert.out_idx) { - // Linked to previous start event's vertex. - // Handle bad down cusp. - // \ \/ - // \ b - // \--a - // \ v Bad down cusp. - // \ /\ / - // \ / \ / - - // The bad cusp is linked to the lowest visible vertex seen from the left edge. If vertex (a) exists that would be connected to the cusp. - // If it didn't exist the next lowest would be (b) a bad up cusp. - - const left_left_id = sweep_edges.getPrev(mb_left_id.?).?; - const left_left = sweep_edges.getPtrNoCheck(left_left_id); - log("handle bad down cusp {}", .{left_left.lowest_right_vert_idx}); - if (left_left.lowest_right_vert_idx == NullId) { - log("expected lowest right vert", .{}); - unreachable; - } - - const low_poly_edge = sweep_edges.getPtrNoCheck(left_left.lowest_right_vert_sweep_edge_id); - if (left_left_id == left_left.lowest_right_vert_sweep_edge_id) { - // Lowest right point does NOT have a right side monotone polygon. - - if (left_left.lowest_right_vert_idx == left_left.vert_idx) { - // Pass on the vertex queue. - new.deferred_queue = low_poly_edge.deferred_queue; - new.deferred_queue_size = low_poly_edge.deferred_queue_size; - new.cur_side = low_poly_edge.cur_side; - low_poly_edge.deferred_queue = NullId; - low_poly_edge.deferred_queue_size = 0; - - self.triangulateLeftStep(new, vert); - - // Cut off the existing left monotone polygon. - left_left.deferred_queue = NullId; - left_left.deferred_queue_size = 0; - left_left.cur_side = .Right; - left_left.enqueueDeferred(self.verts.items[left_left.lowest_right_vert_idx], self); - if (vert.out_idx != self.verts.items[left_left.lowest_right_vert_idx].out_idx) { - left_left.enqueueDeferred(vert, self); - } - left_left.lowest_right_vert_idx = e.vert_idx; - } else { - // Initialize a new right side monotone polygon. - new.enqueueDeferred(self.verts.items[left_left.lowest_right_vert_idx], self); - new.enqueueDeferred(vert, self); - new.cur_side = .Left; - - // Triangulate on the monotone polygon to the left of the lowest right point. - self.triangulateRightStep(left_left, vert); - - left_left.lowest_right_vert_idx = e.vert_idx; - } - } else { - // Lowest right point does have a right side monotone polygon. - - // Most likely a bad up cusp as well, so reset it since connecting to the lowest right fixes it. - left_left.bad_up_cusp_uniq_idx = NullId; - - self.triangulateLeftStep(low_poly_edge, vert); - - // Pass on the vertex queue. - new.deferred_queue = low_poly_edge.deferred_queue; - new.deferred_queue_size = low_poly_edge.deferred_queue_size; - new.cur_side = low_poly_edge.cur_side; - low_poly_edge.deferred_queue = NullId; - low_poly_edge.deferred_queue_size = 0; - - // Triangulate on the monotone polygon to the left of the lowest right point. - self.triangulateRightStep(left_left, vert); - - left_left.lowest_right_vert_idx = e.vert_idx; - left_left.lowest_right_vert_sweep_edge_id = left_left_id; - } - } - } - } - - if (!new.interior_is_left) { - // Even-odd rule. - // Interior is to the right. - - const vert = self.verts.items[e.vert_idx]; - - // Initialize the deferred queue. - if (new.deferred_queue == NullId) { - new.enqueueDeferred(vert, self); - new.cur_side = .Left; - - log("initialize queue", .{}); - new.dumpQueue(self); - } - - // The lowest right vert is set initializes to itself. - new.lowest_right_vert_idx = e.vert_idx; - new.lowest_right_vert_sweep_edge_id = new_id; - } else { - // Interior is to the left. - } - - // Check intersection with the left. Fetch left again since it could be removed from an up cusp. - mb_left_id = sweep_edges.getPrev(new_id); - if (mb_left_id != null) { - log("check left intersect", .{}); - var left = sweep_edges.getPtrNoCheck(mb_left_id.?); - const res = computeTwoEdgeIntersect(new.edge, left.edge); - log("{},{}->{},{} {},{}->{},{} {} {}", .{ new.edge.start_pos[0], new.edge.start_pos[1], new.edge.end_pos[0], new.edge.end_pos[1], left.edge.start_pos[0], left.edge.start_pos[1], left.edge.end_pos[0], left.edge.end_pos[1], res.has_intersect, res.t }); - if (res.has_intersect and res.t > 0 and res.t < 1) { - self.handleIntersectForStartEvent(new, left, res, Vec2{ e.vert_x, e.vert_y }); - } - } - const mb_right_id = sweep_edges.getNext(new_id); - if (mb_right_id != null) { - // Check for intersection with the edge to the right. - log("check right intersect", .{}); - - // TODO: Is there a preliminary check to avoid doing the math? One idea is to check the x_slopes but it would need to know - // if the compared edge is pointing down or up. - const right = sweep_edges.getPtrNoCheck(mb_right_id.?); - const res = computeTwoEdgeIntersect(new.edge, right.edge); - if (res.has_intersect and res.t > 0 and res.t < 1) { - self.handleIntersectForStartEvent(new, right, res, Vec2{ e.vert_x, e.vert_y }); - } - } - } else { - // End event. - - if (e.invalidated) { - // This end event was invalidated from an intersection event. - return; - } - - const active_id = findSweepEdgeForEndEvent(sweep_edges, e) orelse { - log("polygons: {any}", .{self.cur_polys}); - @panic("expected active edge"); - }; - const active = sweep_edges.getPtrNoCheck(active_id); - log("active {} {}", .{ active.vert_idx, active.to_vert_idx }); - - const vert = self.verts.items[e.vert_idx]; - - if (active.interior_is_left) { - // Interior is to the left. - - log("interior to the left {}", .{active_id}); - - const left_id = sweep_edges.getPrev(active_id).?; - const left = sweep_edges.getPtrNoCheck(left_id); - - // Check if it has closed the polygon. (up cusp) - if (vert.out_idx == left.end_event_vert_uniq_idx) { - // Check for bad up cusp. - if (left.bad_up_cusp_uniq_idx != NullId) { - const bad_right = sweep_edges.getPtrNoCheck(left.bad_up_cusp_right_sweep_edge_id); - // Close off monotone polygon to the right of the bad up cusp. - self.triangulateLeftStep(bad_right, vert); - sweep_edges.removeDetached(left.bad_up_cusp_right_sweep_edge_id); - left.bad_up_cusp_uniq_idx = NullId; - left.lowest_right_vert_idx = e.vert_idx; - left.lowest_right_vert_sweep_edge_id = left_id; - if (bad_right.deferred_queue_size >= 3) { - log("{any}", .{self.out_idxes.items}); - bad_right.dumpQueue(self); - @panic("did not expect left over vertices"); - } - } - if (left.deferred_queue_size >= 3) { - log("{} {any}", .{ left_id, self.out_idxes.items }); - left.dumpQueue(self); - @panic("did not expect left over vertices"); - } - // Remove the left edge and this edge. - sweep_edges.remove(left_id) catch unreachable; - sweep_edges.remove(active_id) catch unreachable; - } else { - // Regular right side vertex. - - // Check for bad up cusp. - if (left.bad_up_cusp_uniq_idx != NullId) { - const bad_right = sweep_edges.getPtrNoCheck(left.bad_up_cusp_right_sweep_edge_id); - // Close off monotone polygon to the right of the bad up cusp. - self.triangulateRightStep(bad_right, vert); - left.lowest_right_vert_idx = vert.idx; - left.lowest_right_vert_sweep_edge_id = left_id; - sweep_edges.removeDetached(left.bad_up_cusp_right_sweep_edge_id); - left.bad_up_cusp_uniq_idx = NullId; - if (bad_right.deferred_queue_size >= 3) { - log("{any}", .{self.out_idxes.items}); - bad_right.dumpQueue(self); - @panic("did not expect left over vertices"); - } - } - // Left belongs to the same monotone polygon. - self.triangulateRightStep(left, vert); - - // Edge is only removed by the next connecting edge. - active.end_event_vert_uniq_idx = vert.out_idx; - } - } else { - // Interior is to the right. - log("interior to the right {}", .{active_id}); - - const mb_left_id = sweep_edges.getPrev(active_id); - - // Check to fix a bad up cusp to the right. - if (active.bad_up_cusp_uniq_idx != NullId) { - const bad_right = sweep_edges.getPtrNoCheck(active.bad_up_cusp_right_sweep_edge_id); - // Close off monotone polygon in between this active edge and the bad up cusp. - self.triangulateLeftStep(active, vert); - if (active.deferred_queue_size >= 3) { - log("{any}", .{self.out_idxes.items}); - active.dumpQueue(self); - @panic("did not expect left over vertices"); - } - active.lowest_right_vert_idx = NullId; - active.lowest_right_vert_sweep_edge_id = NullId; - sweep_edges.removeDetached(active.bad_up_cusp_right_sweep_edge_id); - active.bad_up_cusp_uniq_idx = NullId; - // Extend the monotone polygon to the right of the bad up cusp to this vertex. - self.triangulateLeftStep(bad_right, vert); - active.deferred_queue = bad_right.deferred_queue; - active.deferred_queue_size = bad_right.deferred_queue_size; - } else { - active.dumpQueue(self); - self.triangulateLeftStep(active, vert); - active.dumpQueue(self); - } - - // Check if this forms a bad up cusp with the right edge to the left monotone polygon. - var removed = false; - if (mb_left_id != null) { - log("check to set bad up cusp", .{}); - const left = sweep_edges.getNoCheck(mb_left_id.?); - // dump(edgeToString(left_right_edge.edge)) - if (vert.out_idx == left.end_event_vert_uniq_idx) { - const left_left_id = sweep_edges.getPrev(mb_left_id.?).?; - const left_left = sweep_edges.getPtrNoCheck(left_left_id); - // Bad up cusp. - left_left.bad_up_cusp_uniq_idx = vert.out_idx; - left_left.bad_up_cusp_right_sweep_edge_id = active_id; - left_left.lowest_right_vert_idx = e.vert_idx; - left_left.lowest_right_vert_sweep_edge_id = active_id; - - // Remove the left edge. - sweep_edges.remove(mb_left_id.?) catch unreachable; - // Detach this edge, remove it when the bad up cusp is fixed. - sweep_edges.detach(active_id) catch unreachable; - removed = true; - // Continue. - } - } - - if (!removed) { - // Don't remove the left edge of this sub-polygon yet. - // Record the end event's vert so the next start event that continues from this vert can persist the deferred vertices and remove this sweep edge. - // It can also be removed by a End right edge. - active.end_event_vert_uniq_idx = vert.out_idx; - } - } - } - } - - inline fn addTriangle(self: *Self, v1_out: u16, v2_out: u16, v3_out: u16) void { - log("triangle {} {} {}", .{ v1_out, v2_out, v3_out }); - self.out_idxes.appendSlice(&.{ v1_out, v2_out, v3_out }) catch unreachable; - } - - /// Parses the polygon pts and adds the initial events into the priority queue. - fn initEvents(self: *Self, polygons: []const []const Vec2) void { - const t_ = trace(@src()); - defer t_.end(); - for (polygons) |polygon| { - // Find the starting point that is not equal to the last vertex point. - // Since we are adding events to a priority queue, we need to make sure each add is final. - var start_idx: u16 = 0; - const last_pt = polygon[polygon.len - 1]; - while (start_idx < polygon.len) : (start_idx += 1) { - const pt = polygon[start_idx]; - - // Add internal vertex even though we are skipping events for it to keep the idxes consistent with the input. - const v = InternalVertex{ - .pos = pt, - .idx = start_idx, - }; - self.verts.append(v) catch unreachable; - - if (@fabs(last_pt[0] - pt[0]) > 1e-4 or @fabs(last_pt[1] - pt[1]) > 1e-4) { - break; - } else { - self.verts.items[self.verts.items.len - 1].idx = NullId; - } - } - - var last_v = self.verts.items[start_idx]; - var last_v_idx = start_idx; - - var i: u16 = start_idx + 1; - while (i < polygon.len) : (i += 1) { - const v_idx = @intCast(u16, self.verts.items.len); - const v = InternalVertex{ - .pos = polygon[i], - .idx = i, - }; - self.verts.append(v) catch unreachable; - - if (@fabs(last_v.pos[0] - v.pos[0]) < 1e-4 and @fabs(last_v.pos[1] - v.pos[1]) < 1e-4) { - // Don't connect two vertices that are on top of each other. - // Allowing this would require edge cases during event processing to make sure things don't break. - // Push the vertex in anyway so there is consistency with the input. - self.verts.items[self.verts.items.len - 1].idx = NullId; - continue; - } - - const prev_edge = Edge.init(last_v_idx, last_v, v_idx, v); - const event1_idx = @intCast(u32, self.events.items.len); - var event1: Event = undefined; - var event2: Event = undefined; - if (Event.isStartEvent(last_v_idx, prev_edge, self.verts.items)) { - event1 = Event.init(last_v, prev_edge, .Start); - event2 = Event.init(v, prev_edge, .End); - event1.end_event_idx = event1_idx + 1; - } else { - event1 = Event.init(last_v, prev_edge, .End); - event2 = Event.init(v, prev_edge, .Start); - event2.end_event_idx = event1_idx; - } - self.events.append(event1) catch unreachable; - self.event_q.add(event1_idx) catch unreachable; - self.events.append(event2) catch unreachable; - self.event_q.add(event1_idx + 1) catch unreachable; - last_v = v; - last_v_idx = v_idx; - } - // Link last pt to start pt. - const edge = Edge.init(last_v_idx, last_v, start_idx, self.verts.items[start_idx]); - const event1_idx = @intCast(u32, self.events.items.len); - var event1: Event = undefined; - var event2: Event = undefined; - if (Event.isStartEvent(last_v_idx, edge, self.verts.items)) { - event1 = Event.init(last_v, edge, .Start); - event2 = Event.init(self.verts.items[start_idx], edge, .End); - event1.end_event_idx = event1_idx + 1; - } else { - event1 = Event.init(last_v, edge, .End); - event2 = Event.init(self.verts.items[start_idx], edge, .Start); - event2.end_event_idx = event1_idx; - } - self.events.append(event1) catch unreachable; - self.event_q.add(event1_idx) catch unreachable; - self.events.append(event2) catch unreachable; - self.event_q.add(event1_idx + 1) catch unreachable; - } - } - - fn triangulateLeftStep(self: *Self, left: *SweepEdge, vert: InternalVertex) void { - if (left.cur_side == .Left) { - log("same left side", .{}); - left.dumpQueue(self); - - // Same side. - if (left.deferred_queue_size >= 2) { - var last_id = left.deferred_queue; - var last = self.deferred_verts.getNoCheck(last_id); - if (last.vert_out_idx == vert.out_idx) { - // Ignore this point since it is the same as the last. - return; - } - var cur_id = self.deferred_verts.getNextNoCheck(last_id); - var i: u16 = 0; - while (i < left.deferred_queue_size - 1) : (i += 1) { - log("check to add inward tri {} {}", .{ last_id, cur_id }); - const cur = self.deferred_verts.getNoCheck(cur_id); - const cxp = cross(Vec2{ last.vert_x - cur.vert_x, last.vert_y - cur.vert_y }, Vec2{ vert.pos[0] - last.vert_x, vert.pos[1] - last.vert_y }); - if (cxp < 0) { - // Bends inwards. Fill triangles until we aren't bending inward. - self.addTriangle(vert.out_idx, cur.vert_out_idx, last.vert_out_idx); - self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; - } else { - break; - } - last_id = cur_id; - last = cur; - cur_id = self.deferred_verts.getNextNoCheck(cur_id); - } - if (i > 0) { - const d_vert = self.deferred_verts.insertBeforeHeadNoCheck(last_id, DeferredVertexNode.init(vert)) catch unreachable; - left.deferred_queue = d_vert; - left.deferred_queue_size = left.deferred_queue_size - i + 1; - } else { - left.enqueueDeferred(vert, self); - } - } else { - left.enqueueDeferred(vert, self); - } - } else { - log("changed to left side", .{}); - // Changed to left side. - // Automatically create queue size - 1 triangles. - var last_id = left.deferred_queue; - var last = self.deferred_verts.getNoCheck(last_id); - var cur_id = self.deferred_verts.getNextNoCheck(last_id); - var i: u32 = 0; - while (i < left.deferred_queue_size - 1) : (i += 1) { - const cur = self.deferred_verts.getNoCheck(cur_id); - self.addTriangle(vert.out_idx, last.vert_out_idx, cur.vert_out_idx); - last_id = cur_id; - last = cur; - cur_id = self.deferred_verts.getNextNoCheck(cur_id); - // Delete last after it's assigned to current. - self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; - } - left.dumpQueue(self); - self.deferred_verts.getNodePtrNoCheck(left.deferred_queue).next = NullId; - left.deferred_queue_size = 1; - left.enqueueDeferred(vert, self); - left.cur_side = .Left; - left.dumpQueue(self); - } - } - - fn triangulateRightStep(self: *Self, left: *SweepEdge, vert: InternalVertex) void { - if (left.cur_side == .Right) { - log("right side", .{}); - // Same side. - if (left.deferred_queue_size >= 2) { - var last_id = left.deferred_queue; - var last = self.deferred_verts.getNoCheck(last_id); - if (last.vert_out_idx == vert.out_idx) { - // Ignore this point since it is the same as the last. - return; - } - var cur_id = self.deferred_verts.getNextNoCheck(last_id); - var i: u16 = 0; - while (i < left.deferred_queue_size - 1) : (i += 1) { - const cur = self.deferred_verts.getNoCheck(cur_id); - const cxp = cross(Vec2{ last.vert_x - cur.vert_x, last.vert_y - cur.vert_y }, Vec2{ vert.pos[0] - last.vert_x, vert.pos[1] - last.vert_y }); - if (cxp > 0) { - // Bends inwards. Fill triangles until we aren't bending inward. - self.addTriangle(vert.out_idx, last.vert_out_idx, cur.vert_out_idx); - self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; - } else { - break; - } - last_id = cur_id; - last = cur; - cur_id = self.deferred_verts.getNextNoCheck(cur_id); - } - if (i > 0) { - const d_vert = self.deferred_verts.insertBeforeHeadNoCheck(last_id, DeferredVertexNode.init(vert)) catch unreachable; - left.deferred_queue = d_vert; - left.deferred_queue_size = left.deferred_queue_size - i + 1; - } else { - left.enqueueDeferred(vert, self); - } - } else { - left.enqueueDeferred(vert, self); - } - } else { - log("changed to right side", .{}); - var last_id = left.deferred_queue; - var last = self.deferred_verts.getNoCheck(last_id); - var cur_id = self.deferred_verts.getNextNoCheck(last_id); - var i: u32 = 0; - while (i < left.deferred_queue_size - 1) : (i += 1) { - const cur = self.deferred_verts.getNoCheck(cur_id); - self.addTriangle(vert.out_idx, cur.vert_out_idx, last.vert_out_idx); - last_id = cur_id; - last = cur; - cur_id = self.deferred_verts.getNextNoCheck(cur_id); - // Delete last after it's assigned to current. - self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; - } - self.deferred_verts.getNodePtrNoCheck(left.deferred_queue).next = NullId; - left.deferred_queue_size = 1; - left.enqueueDeferred(vert, self); - left.cur_side = .Right; - left.dumpQueue(self); - } - } - - /// Splits two edges at an intersect point. - /// Assumes sweep edges have not processed their end events so they can be reinserted. - /// Does not add new events if an event already exists to the intersect point. - fn handleIntersectForStartEvent(self: *Self, sweep_edge_a: *SweepEdge, sweep_edge_b: *SweepEdge, intersect: IntersectResult, sweep_vert: Vec2) void { - log("split intersect {}", .{intersect}); - - // The intersect point must lie after the sweep_vert. - if (intersect.y < sweep_vert[1] or (intersect.y == sweep_vert[1] and intersect.x <= sweep_vert[0])) { - return; - } - - var added_events = false; - - // Create new intersect vertex. - const intersect_idx = @intCast(u16, self.verts.items.len); - const intersect_v = InternalVertex{ - .pos = Vec2{ intersect.x, intersect.y }, - .idx = intersect_idx, - }; - self.verts.append(intersect_v) catch unreachable; - - // TODO: Account for floating point error. - const a_to_vert = self.verts.items[sweep_edge_a.to_vert_idx]; - if (a_to_vert.pos[0] != intersect.x or a_to_vert.pos[1] != intersect.y) { - // log(edgeToString(sweep_edge_a.edge)) - log("adding edge a {} to {},{}", .{ sweep_edge_a.vert_idx, intersect.x, intersect.y }); - added_events = true; - - // Invalidate sweep_edge_a's end event since the priority queue can not be modified. - self.events.items[sweep_edge_a.end_event_idx].invalidated = true; - - // Keep original edge orientation when doing the split. - var first_edge: Edge = undefined; - var second_edge: Edge = undefined; - const start = self.verts.items[sweep_edge_a.edge.start_idx]; - const end = self.verts.items[sweep_edge_a.edge.end_idx]; - if (sweep_edge_a.to_vert_idx == sweep_edge_a.edge.start_idx) { - first_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_a.edge.end_idx, end); - second_edge = Edge.init(sweep_edge_a.edge.start_idx, start, intersect_idx, intersect_v); - } else { - first_edge = Edge.init(sweep_edge_a.edge.start_idx, start, intersect_idx, intersect_v); - second_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_a.edge.end_idx, end); - } - - // Update sweep_edge_a to end at the intersect. - sweep_edge_a.edge = first_edge; - const a_orig_to_vert = sweep_edge_a.to_vert_idx; - sweep_edge_a.to_vert_idx = intersect_idx; - - // Insert new sweep_edge_a end event. - const evt_idx = @intCast(u32, self.events.items.len); - var new_evt = Event.init(intersect_v, first_edge, .End); - self.events.append(new_evt) catch unreachable; - self.event_q.add(evt_idx) catch unreachable; - - // Insert start/end event from the intersect to the end of the original sweep_edge_a. - var event1: Event = undefined; - var event2: Event = undefined; - if (Event.isStartEvent(intersect_idx, second_edge, self.verts.items)) { - event1 = Event.init(intersect_v, second_edge, .Start); - event2 = Event.init(self.verts.items[a_orig_to_vert], second_edge, .End); - event1.end_event_idx = evt_idx + 2; - } else { - event1 = Event.init(intersect_v, second_edge, .End); - event2 = Event.init(self.verts.items[a_orig_to_vert], second_edge, .Start); - event2.end_event_idx = evt_idx + 1; - } - self.events.append(event1) catch unreachable; - self.event_q.add(evt_idx + 1) catch unreachable; - self.events.append(event2) catch unreachable; - self.event_q.add(evt_idx + 2) catch unreachable; - } - - const b_to_vert = self.verts.items[sweep_edge_b.to_vert_idx]; - if (b_to_vert.pos[0] != intersect.x or b_to_vert.pos[1] != intersect.y) { - log("adding edge b {} to {},{}", .{ sweep_edge_b.vert_idx, intersect.x, intersect.y }); - added_events = true; - - // Invalidate sweep_edge_b's end event since the priority queue can not be modified. - log("invalidate: {} {}", .{ sweep_edge_b.end_event_idx, self.events.items.len }); - self.events.items[sweep_edge_b.end_event_idx].invalidated = true; - - // Keep original edge orientation when doing the split. - var first_edge: Edge = undefined; - var second_edge: Edge = undefined; - const start = self.verts.items[sweep_edge_b.edge.start_idx]; - const end = self.verts.items[sweep_edge_b.edge.end_idx]; - if (sweep_edge_b.to_vert_idx == sweep_edge_b.edge.start_idx) { - first_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_b.edge.end_idx, end); - second_edge = Edge.init(sweep_edge_b.edge.start_idx, start, intersect_idx, intersect_v); - } else { - first_edge = Edge.init(sweep_edge_b.edge.start_idx, start, intersect_idx, intersect_v); - second_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_b.edge.end_idx, end); - } - - // Update sweep_edge_b to end at the intersect. - sweep_edge_b.edge = first_edge; - const b_orig_to_vert = sweep_edge_b.to_vert_idx; - sweep_edge_b.to_vert_idx = intersect_idx; - - // Insert new sweep_edge_b end event. - const evt_idx = @intCast(u32, self.events.items.len); - var new_evt = Event.init(intersect_v, first_edge, .End); - self.events.append(new_evt) catch unreachable; - self.event_q.add(evt_idx) catch unreachable; - - // Insert start/end event from the intersect to the end of the original sweep_edge_b. - var event1: Event = undefined; - var event2: Event = undefined; - if (Event.isStartEvent(intersect_idx, second_edge, self.verts.items)) { - event1 = Event.init(intersect_v, second_edge, .Start); - event2 = Event.init(self.verts.items[b_orig_to_vert], second_edge, .End); - event1.end_event_idx = evt_idx + 2; - } else { - event1 = Event.init(intersect_v, second_edge, .End); - event2 = Event.init(self.verts.items[b_orig_to_vert], second_edge, .Start); - event2.end_event_idx = evt_idx + 1; - } - self.events.append(event1) catch unreachable; - self.event_q.add(evt_idx + 1) catch unreachable; - self.events.append(event2) catch unreachable; - self.event_q.add(evt_idx + 2) catch unreachable; - } - - if (!added_events) { - // No events were added, revert adding intersect point. - _ = self.verts.pop(); - } - } -}; - -fn edgeToString(edge: Edge) []const u8 { - const S = struct { - var buf: [100]u8 = undefined; - }; - return std.fmt.bufPrint(&S.buf, "{} ({},{}) -> {} ({},{})", .{ edge.start_idx, edge.start_pos.x, edge.start_pos.y, edge.end_idx, edge.end_pos.x, edge.end_pos.y }) catch unreachable; -} - -fn compareEventIdx(events: *std.ArrayList(Event), a: u32, b: u32) std.math.Order { - return compareEvent(events.items[a], events.items[b]); -} - -/// Sort verts by y asc then x asc. Resolve same position by looking at the edge's xslope and whether it is active. -fn compareEvent(a: Event, b: Event) std.math.Order { - if (a.vert_y < b.vert_y) { - return .lt; - } else if (a.vert_y > b.vert_y) { - return .gt; - } else { - if (a.vert_x < b.vert_x) { - return .lt; - } else if (a.vert_x > b.vert_x) { - return .gt; - } else { - if (a.tag == .End and b.tag == .Start) { - return .lt; - } else if (a.tag == .Start and b.tag == .End) { - return .gt; - } else if (a.tag == .End and b.tag == .End) { - if (a.edge.x_slope > b.edge.x_slope) { - return .lt; - } else if (a.edge.x_slope < b.edge.x_slope) { - return .gt; - } else { - return .eq; - } - } else { - if (a.edge.x_slope < b.edge.x_slope) { - return .lt; - } else if (a.edge.x_slope > b.edge.x_slope) { - return .gt; - } else { - return .eq; - } - } - } - } -} - -/// Compare SweepEdges for insertion. Each sweep edge should be unique since the rb tree doesn't support duplicate values. -/// The slopes from the current sweep edges are used to find their x-intersect along the event's y line. -/// If compared sweep edge is a horizontal line, return gt so it's inserted after it. The horizontal edge can be assumed to intersect with the target event or it wouldn't be in the sweep edges. -fn compareSweepEdge(_: SweepEdge, b: SweepEdge, evt: Event) std.math.Order { - if (!b.edge.is_horiz) { - const x_intersect = b.edge.x_slope * (evt.vert_y - b.edge.start_pos[1]) + b.edge.start_pos[0]; - if (@fabs(evt.vert_x - x_intersect) < SweepEdgeApproxEpsilon) { - // Since there is a chance of having floating point error, check with an epsilon. - // Always return .gt so the left sweep edge can be reliably checked for a joining edge. - return .gt; - } else { - if (evt.vert_x < x_intersect) { - return .lt; - } else if (evt.vert_x > x_intersect) { - return .gt; - } else { - unreachable; - } - } - } else { - return .gt; - } -} - -fn findSweepEdgeForEndEvent(sweep_edges: *RbTree(u16, SweepEdge, Event, compareSweepEdge), e: Event) ?u16 { - const target = Vec2{ e.vert_x, e.vert_y }; - const dummy = SweepEdge{ - .edge = undefined, - .start_event_vert_uniq_idx = undefined, - .vert_idx = undefined, - .to_vert_idx = undefined, - .end_event_vert_uniq_idx = undefined, - .deferred_queue = undefined, - .deferred_queue_size = undefined, - .cur_side = undefined, - .bad_up_cusp_uniq_idx = undefined, - .bad_up_cusp_right_sweep_edge_id = undefined, - .lowest_right_vert_idx = undefined, - .lowest_right_vert_sweep_edge_id = undefined, - .interior_is_left = undefined, - .end_event_idx = undefined, - }; - log("find {},{}", .{ target[0], target[1] }); - var mb_parent: ?u16 = undefined; - var is_left: bool = undefined; - var start_id: u16 = undefined; - if (sweep_edges.lookupCustomLoc(dummy, target, compareSweepEdgeApprox, &mb_parent, &is_left)) |id| { - start_id = id; - } else { - // It's possible that we can't find the sweep edge based on x intersect due to floating point error. - // Start linear search at the parent. - if (mb_parent) |parent| { - start_id = parent; - } else { - return null; - } - } - - // Given a start index where a group of verts could have approx the same x-intersect value, find the one with the exact vert and to_vert. - // The search ends on left/right when the x-intersect suddenly becomes greater than the epsilon. - var se = sweep_edges.getNoCheck(start_id); - if (se.to_vert_idx == e.vert_idx and se.vert_idx == e.to_vert_idx) { - return start_id; - } - log("skip edge {} -> {}", .{ se.vert_idx, se.to_vert_idx }); - // Search left. - var mb_cur = sweep_edges.getPrev(start_id); - while (mb_cur) |cur| { - se = sweep_edges.getNoCheck(cur); - const x_intersect = getXIntersect(se.edge, target); - if (@fabs(e.vert_x - x_intersect) > SweepEdgeApproxEpsilon) { - break; - } else if (se.to_vert_idx == e.vert_idx and se.vert_idx == e.to_vert_idx) { - return cur; - } - mb_cur = sweep_edges.getPrev(cur); - } - // Search right. - mb_cur = sweep_edges.getNext(start_id); - while (mb_cur) |cur| { - se = sweep_edges.getNoCheck(cur); - const x_intersect = getXIntersect(se.edge, target); - if (@fabs(e.vert_x - x_intersect) > SweepEdgeApproxEpsilon) { - break; - } else if (se.to_vert_idx == e.vert_idx and se.vert_idx == e.to_vert_idx) { - return cur; - } - mb_cur = sweep_edges.getNext(cur); - } - return null; -} - -const SweepEdgeApproxEpsilon: f32 = 1e-4; - -/// Finds the first edge with x-intersect that approximates the provided target vert's x. -/// This is needed since floating point error can lead to inconsistent divide and conquer for x-intersects that are close together (eg. two edges stemming from one vertex) -/// A follow up routine to find the exact edge should be run afterwards. -fn compareSweepEdgeApprox(_: SweepEdge, b: SweepEdge, target: Vec2) std.math.Order { - const x_intersect = getXIntersect(b.edge, target); - // Relax the epsilon since larger floating point error can happen here. In the end it's surroundings is verified by linear search. - if (@fabs(target[0] - x_intersect) < SweepEdgeApproxEpsilon) { - return .eq; - } else if (target[0] < x_intersect) { - return .lt; - } else { - return .gt; - } -} - -// Assumes there is an intersect. -fn getXIntersect(edge: Edge, target: Vec2) f32 { - if (!edge.is_horiz) { - return edge.x_slope * (target[1] - edge.start_pos[1]) + edge.start_pos[0]; - } else { - return target[0]; - } -} - -const EventTag = enum(u1) { - Start = 0, - End = 1, -}; - -/// Polygon verts and edges are reduced to events. -/// Each event is centered on a vertex and has an outgoing or incoming edge. -/// If the edge is above the vertex, it's considered a End event. -/// If the edge is below the vertex, it's considered a Start event. -/// Events are sorted by y asc and the x asc. -/// To order events on the same vertex, the event with an active edge has priority. -/// If both events have active edges (both End events), the one that has a greater x-slope comes first. This means an active edge to the left comes first. -/// This helps end processing since the left edge still exists and contains state information about that fill region. -/// If both events have non active edges (both Start events), the one that has a lesser x-slope comes first. -const Event = struct { - const Self = @This(); - - /// The idx of the internal vertex that this event fires on. - vert_idx: u16, - - /// Duped vert pos for compare func. - vert_x: f32, - vert_y: f32, - - tag: EventTag, - - edge: Edge, - - to_vert_idx: u16, - - /// If this is a start event, end_event_idx will point to the corresponding end event. - end_event_idx: u32, - - /// Since an intersection creates new events and can not modify the priority queue, - /// this flag is used to invalid an existing end event. - invalidated: bool, - - fn init(vert: InternalVertex, edge: Edge, tag: EventTag) Self { - var new = Self{ - .vert_idx = vert.idx, - .vert_x = vert.pos[0], - .vert_y = vert.pos[1], - .edge = edge, - .tag = tag, - .to_vert_idx = undefined, - .end_event_idx = std.math.maxInt(u32), - .invalidated = false, - }; - if (edge.start_idx == vert.idx) { - new.to_vert_idx = edge.end_idx; - } else { - new.to_vert_idx = edge.start_idx; - } - return new; - } - - fn isStartEvent(vert_idx: u16, edge: Edge, verts: []const InternalVertex) bool { - const vert = verts[vert_idx]; - // The start and end vertex of an edge is not to be confused with the EventType. - // It is used to determine if the edge is above or below the vertex point. - if (edge.start_idx == vert_idx) { - const end_v = verts[edge.end_idx]; - if (end_v.pos[1] < vert.pos[1] or (end_v.pos[1] == vert.pos[1] and end_v.pos[0] < vert.pos[0])) { - return false; - } else { - return true; - } - } else { - const start_v = verts[edge.start_idx]; - if (start_v.pos[1] < vert.pos[1] or (start_v.pos[1] == vert.pos[1] and start_v.pos[0] < vert.pos[0])) { - return false; - } else { - return true; - } - } - } -}; - -const Side = enum(u1) { - Left = 0, - Right = 1, -}; - -pub const SweepEdge = struct { - const Self = @This(); - - edge: Edge, - start_event_vert_uniq_idx: u16, - vert_idx: u16, - to_vert_idx: u16, - - end_event_idx: u32, - - /// The End event that marks this edge for removal. This is set in the End event. - end_event_vert_uniq_idx: u16, - - /// Points to the head vertex. - deferred_queue: DeferredVertexNodeId, - - /// Current size of the queue. - deferred_queue_size: u16, - - /// The current side being processed for monotone triangulation. - cur_side: Side, - - /// Last seen bad up cusp. - bad_up_cusp_uniq_idx: u16, - bad_up_cusp_right_sweep_edge_id: u16, - lowest_right_vert_idx: u16, - lowest_right_vert_sweep_edge_id: u16, - - /// Store the winding. This would be used by the fill rule to determine if the interior is to the left or right. - interior_is_left: bool, - - /// A sweep edge is created from a Start event. - fn init(e: Event, verts: []const InternalVertex) Self { - const e_vert = verts[e.vert_idx]; - return .{ - .edge = e.edge, - .start_event_vert_uniq_idx = e_vert.out_idx, - .vert_idx = e.vert_idx, - .to_vert_idx = e.to_vert_idx, - .end_event_idx = e.end_event_idx, - .end_event_vert_uniq_idx = std.math.maxInt(u16), - .deferred_queue = NullId, - .deferred_queue_size = 0, - .cur_side = .Left, - .bad_up_cusp_uniq_idx = NullId, - .bad_up_cusp_right_sweep_edge_id = NullId, - .lowest_right_vert_idx = NullId, - .lowest_right_vert_sweep_edge_id = NullId, - .interior_is_left = false, - }; - } - - fn enqueueDeferred(self: *Self, vert: InternalVertex, tess: *Tessellator) void { - const node = DeferredVertexNode.init(vert); - self.deferred_queue = tess.deferred_verts.insertBeforeHeadNoCheck(self.deferred_queue, node) catch unreachable; - self.deferred_queue_size += 1; - } - - fn verifySize(self: Self, tess: *Tessellator) void { - var cur = self.deferred_queue; - var act_size: u32 = 0; - while (cur != NullId) { - act_size += 1; - cur = tess.deferred_verts.getNextNoCheck(cur); - } - if (self.deferred_queue_size != act_size) { - log("expected {}, actual {}", .{ self.deferred_queue_size, act_size }); - unreachable; - } - } - - fn dumpQueue(self: Self, tess: *Tessellator) void { - var buf: [200]u8 = undefined; - var buf_stream = std.io.fixedBufferStream(&buf); - var writer = buf_stream.writer(); - var cur_id = self.deferred_queue; - while (cur_id != NullId) { - const cur = tess.deferred_verts.getNoCheck(cur_id); - std.fmt.format(writer, "{},", .{cur.vert_idx}) catch unreachable; - const last = cur_id; - cur_id = tess.deferred_verts.getNextNoCheck(cur_id); - if (last == cur_id) { - std.fmt.format(writer, "repeat pt - bad state", .{}) catch unreachable; - break; - } - } - var side_str: []const u8 = if (self.cur_side == .Right) "right" else "left"; - log("size: {}, side: {s}, idxes: {s}", .{ self.deferred_queue_size, side_str, buf[0..buf_stream.pos] }); - } -}; - -/// This contains a vertex that still needs to be triangulated later when it can. -/// It is linked together in a singly linked list queue and is designed to behave like the monotone triangulation queue. -/// Since the triangulator does everything in one pass for complex polygons, every monotone polygon span in the sweep edges -/// needs to keep track of their deferred vertices since the edges are short lived. -pub const DeferredVertexNode = struct { - const Self = @This(); - - vert_idx: u16, - - // Duped vars. - vert_out_idx: u16, - vert_x: f32, - vert_y: f32, - - fn init(vert: InternalVertex) Self { - return .{ - .vert_idx = vert.idx, - .vert_out_idx = vert.out_idx, - .vert_x = vert.pos[0], - .vert_y = vert.pos[1], - }; - } -}; - -/// Contains start/end InternalVertex. -pub const Edge = struct { - const Self = @This(); - - /// Start vertex index. - start_idx: u16, - /// End vertex index. - end_idx: u16, - - /// Duped start_pos for compareSweepEdge, getXIntersect. - start_pos: Vec2, - end_pos: Vec2, - - /// Vector from start to end pos. - vec: Vec2, - - /// Change of x with respect to y. - x_slope: f32, - - /// Whether the edge is horizontal. - /// TODO: This may not be needed anymore. - is_horiz: bool, - - fn init(start_idx: u16, start_v: InternalVertex, end_idx: u16, end_v: InternalVertex) Self { - var new = Self{ - .start_idx = start_idx, - .end_idx = end_idx, - .start_pos = start_v.pos, - .end_pos = end_v.pos, - .vec = end_v.pos - start_v.pos, - .x_slope = undefined, - .is_horiz = undefined, - }; - if (new.vec[1] != 0) { - new.x_slope = new.vec[0] / new.vec[1]; - new.is_horiz = false; - } else { - new.x_slope = std.math.f32_max; - new.is_horiz = true; - } - return new; - } -}; - -/// Internal verts. During triangulation, these are using for event computations so there can be duplicates points. -pub const InternalVertex = struct { - pos: Vec2, - /// This is the index of the vertex from the given polygon. - idx: u16, - /// The vertex index of the resulting buffer. Set during process event. - out_idx: u16 = undefined, -}; - -/// Avoids division by zero. -/// https://stackoverflow.com/questions/563198 -/// For segments: p, p + r, q, q + s -/// To find t, u for p + tr, q + us -/// t = (q − p) X s / (r X s) -/// u = (q − p) X r / (r X s) -fn computeTwoEdgeIntersect(p: Edge, q: Edge) IntersectResult { - const t__ = trace(@src()); - defer t__.end(); - const r_s = cross(p.vec, q.vec); - if (r_s == 0) { - return IntersectResult.initNull(); - } - const qmp = Vec2{ q.start_pos[0] - p.start_pos[0], q.start_pos[1] - p.start_pos[1] }; - const qmp_r = cross(qmp, p.vec); - const u = qmp_r / r_s; - if (u >= 0 and u <= 1) { - // Must check intersect point is also on p. - const qmp_s = cross(qmp, q.vec); - const t_ = qmp_s / r_s; - if (t_ >= 0 and t_ <= 1) { - return .{ - .x = q.start_pos[0] + q.vec[0] * u, - .y = q.start_pos[1] + q.vec[1] * u, - .t = t_, - .u = u, - .has_intersect = true, - }; - } else { - return IntersectResult.initNull(); - } - } else { - return IntersectResult.initNull(); - } -} - -const IntersectResult = struct { - x: f32, - y: f32, - t: f32, - u: f32, - has_intersect: bool, - - fn initNull() IntersectResult { - return .{ - .x = undefined, - .y = undefined, - .t = undefined, - .u = undefined, - .has_intersect = false, - }; - } -}; - -/// Just check triangle count. -/// TODO: Verify all triangles take up the entire space. -fn testLarge(polygon: []const f32, exp_tri_count: u32) !void { - var polygon_buf = std.ArrayList(Vec2).init(t.allocator); - defer polygon_buf.deinit(); - var i: u32 = 0; - while (i < polygon.len) : (i += 2) { - polygon_buf.append(Vec2{ polygon[i], polygon[i + 1] }) catch unreachable; - } - var tessellator: Tessellator = undefined; - tessellator.init(t.allocator); - defer tessellator.deinit(); - tessellator.triangulatePolygon(polygon_buf.items); - try t.expectEqual(tessellator.out_idxes.items.len / 3, exp_tri_count); -} - -fn testSimple(polygon: []const f32, exp_verts: []const f32, exp_idxes: []const u16) !void { - var polygon_buf = std.ArrayList(Vec2).init(t.allocator); - defer polygon_buf.deinit(); - var i: u32 = 0; - while (i < polygon.len) : (i += 2) { - polygon_buf.append(Vec2{ polygon[i], polygon[i + 1] }) catch unreachable; - } - var tessellator: Tessellator = undefined; - tessellator.init(t.allocator); - defer tessellator.deinit(); - tessellator.triangulatePolygon(polygon_buf.items); - - var exp_verts_buf = std.ArrayList(Vec2).init(t.allocator); - defer exp_verts_buf.deinit(); - i = 0; - while (i < exp_verts.len) : (i += 2) { - exp_verts_buf.append(Vec2{ exp_verts[i], exp_verts[i + 1] }) catch unreachable; - } - try t.expectEqualSlices(Vec2, tessellator.out_verts.items, exp_verts_buf.items); - // log("{any}", .{tessellator.out_idxes.items}); - try t.expectEqualSlices(u16, tessellator.out_idxes.items, exp_idxes); -} - -test "One triangle ccw." { - try testSimple(&.{ - 100, 0, - 0, 0, - 0, 100, - }, &.{ - 0, 0, - 100, 0, - 0, 100, - }, &.{ 2, 1, 0 }); -} - -test "One triangle cw." { - try testSimple(&.{ - 100, 0, - 0, 100, - 0, 0, - }, &.{ - 0, 0, - 100, 0, - 0, 100, - }, &.{ 2, 1, 0 }); -} - -test "Square." { - try testSimple(&.{ - 0, 0, - 100, 0, - 100, 100, - 0, 100, - }, &.{ - 0, 0, - 100, 0, - 0, 100, - 100, 100, - }, &.{ - 2, 1, 0, - 3, 1, 2, - }); -} - -test "Pentagon." { - try testSimple(&.{ - 100, 0, - 200, 100, - 200, 200, - 0, 200, - 0, 100, - }, &.{ - 100, 0, - 0, 100, - 200, 100, - 0, 200, - 200, 200, - }, &.{ - 2, 0, 1, - 3, 2, 1, - 4, 2, 3, - }); -} - -test "Hexagon." { - try testSimple(&.{ - 100, 0, - 200, 100, - 200, 200, - 100, 300, - 0, 200, - 0, 100, - }, &.{ - 100, 0, - 0, 100, - 200, 100, - 0, 200, - 200, 200, - 100, 300, - }, &.{ - 2, 0, 1, - 3, 2, 1, - 4, 2, 3, - 5, 4, 3, - }); -} - -test "Octagon." { - try testSimple(&.{ - 100, 0, - 200, 0, - 300, 100, - 300, 200, - 200, 300, - 100, 300, - 0, 200, - 0, 100, - }, &.{ - 100, 0, - 200, 0, - 0, 100, - 300, 100, - 0, 200, - 300, 200, - 100, 300, - 200, 300, - }, &.{ - 2, 1, 0, - 3, 1, 2, - 4, 3, 2, - 5, 3, 4, - 6, 5, 4, - 7, 5, 6, - }); -} - -test "Rhombus." { - try testSimple(&.{ - 100, 0, - 200, 100, - 100, 200, - 0, 100, - }, &.{ - 100, 0, - 0, 100, - 200, 100, - 100, 200, - }, &.{ - 2, 0, 1, - 3, 2, 1, - }); -} - -// Tests monotone partition with bad up cusp and valid right angle. -test "Square with concave top side." { - try testSimple(&.{ - 0, 0, - 100, 100, - 200, 0, - 200, 200, - 0, 200, - }, &.{ - 0, 0, - 200, 0, - 100, 100, - 0, 200, - 200, 200, - }, &.{ - 3, 2, 0, - 4, 2, 3, - 4, 1, 2, - }); -} - -// Tests monotone partition with bad down cusp and valid right angle. -test "Square with concave bottom side." { - try testSimple(&.{ - 0, 0, - 200, 0, - 200, 200, - 100, 100, - 0, 200, - }, &.{ - 0, 0, - 200, 0, - 100, 100, - 0, 200, - 200, 200, - }, &.{ - 2, 1, 0, - 3, 2, 0, - 4, 1, 2, - }); -} - -// Tests monotone partition with bad up cusp and valid up cusp. -test "V shape." { - try testSimple(&.{ - 0, 0, - 100, 100, - 200, 0, - 100, 200, - }, &.{ - 0, 0, - 200, 0, - 100, 100, - 100, 200, - }, &.{ - 3, 2, 0, - 3, 1, 2, - }); -} - -// Tests monotone partition with bad down cusp and valid up cusp. -test "Upside down V shape." { - try testSimple(&.{ - 100, 0, - 200, 200, - 100, 100, - 0, 200, - }, &.{ - 100, 0, - 100, 100, - 0, 200, - 200, 200, - }, &.{ - 2, 1, 0, - 3, 0, 1, - }); -} - -// Tests the sweep line with alternating interior/exterior sides. -test "Clockwise spiral." { - try testSimple(&.{ - 0, 0, - 500, 0, - 500, 500, - 200, 500, - 200, 200, - 300, 200, - 300, 400, - 400, 400, - 400, 100, - 100, 100, - 100, 500, - 0, 500, - }, &.{ - 0, 0, - 500, 0, - 100, 100, - 400, 100, - 200, 200, - 300, 200, - 300, 400, - 400, 400, - 0, 500, - 100, 500, - 200, 500, - 500, 500, - }, &.{ - 2, 1, 0, - 3, 1, 2, - 6, 5, 4, - 7, 1, 3, - 8, 2, 0, - 9, 2, 8, - 10, 7, 6, - 10, 6, 4, - 11, 7, 10, - 11, 1, 7, - }); -} - -// Tests the sweep line with alternating interior/exterior sides. -test "CCW spiral." { - try testSimple(&.{ - 0, 0, - 500, 0, - 500, 500, - 400, 500, - 400, 100, - 100, 100, - 100, 400, - 200, 400, - 200, 200, - 300, 200, - 300, 500, - 0, 500, - }, &.{ - 0, 0, - 500, 0, - 100, 100, - 400, 100, - 200, 200, - 300, 200, - 100, 400, - 200, 400, - 0, 500, - 300, 500, - 400, 500, - 500, 500, - }, &.{ - 2, 1, 0, - 3, 1, 2, - 6, 2, 0, - 7, 5, 4, - 8, 7, 6, - 8, 6, 0, - 9, 7, 8, - 9, 5, 7, - 10, 1, 3, - 11, 1, 10, - }); -} - -test "Overlapping point." { - try testSimple(&.{ - 0, 0, - 100, 100, - 200, 0, - 200, 200, - 100, 100, - 0, 200, - }, &.{ - 0, 0, - 200, 0, - 100, 100, - 0, 200, - 200, 200, - }, &.{ - 3, 2, 0, - 4, 1, 2, - }); -} - -// Different windings on split polygons. -test "Self intersecting polygon." { - try testSimple(&.{ - 0, 200, - 0, 100, - 200, 100, - 200, 0, - }, &.{ - 200, 0, - 0, 100, - 100, 100, - 200, 100, - 0, 200, - }, &.{ - 3, 0, 2, - 4, 2, 1, - }); -} - -// Test evenodd rule. -test "Overlapping triangles." { - try testSimple(&.{ - 0, 100, - 200, 0, - 200, 200, - 0, 100, - 250, 75, - 250, 125, - 0, 100, - }, &.{ - 200, 0, - 250, 75, - 200, 80, - 0, 100, - 200, 120, - 250, 125, - 200, 200, - }, &.{ - 3, 2, 0, - 4, 1, 2, - 5, 1, 4, - 6, 4, 3, - }); -} - -// Begin mapbox test cases. - -test "bad-diagonals.json" { - try testSimple(&.{ - 440, 4152, - 440, 4208, - 296, 4192, - 368, 4192, - 400, 4200, - 400, 4176, - 368, 4192, - 296, 4192, - 264, 4200, - 288, 4160, - 296, 4192, - }, &.{ - 440, 4152, - 288, 4160, - 400, 4176, - 296, 4192, - 368, 4192, - 264, 4200, - 400, 4200, - 440, 4208, - }, &.{ - 3, 2, 0, - 4, 2, 3, - 5, 3, 1, - 6, 4, 3, - 6, 0, 2, - 7, 6, 3, - 7, 0, 6, - }); -} - -// TODO: dude.json - -// Case by case examples. - -test "Rectangle with bottom-left wedge." { - try testSimple(&.{ - 56, 22, - 111, 22, - 111, 44, - 37, 44, - 56, 32, - }, &.{ - 56, 22, - 111, 22, - 56, 32, - 37, 44, - 111, 44, - }, &.{ - 2, 1, 0, - 3, 1, 2, - 4, 1, 3, - }); -} - -test "Tiger big part." { - try testLarge(&.{ - -129.83, 103.06, -129.83, 103.06, -128.36, 113.62, -126.60, 118.80, -126.60, 118.80, -127.33, 125.99, -125.81, 135.32, -121.40, 144.40, -121.40, 144.40, -121.18, 151.03, -120.20, 154.80, -120.20, 154.80, -115.56, 161.53, -111.40, 164.00, -111.40, 164.00, -99.95, 166.93, -88.93, 169.12, -88.93, 169.12, -82.12, 176.08, -77.01, 183.74, -74.79, 190.23, -75.00, 196.00, -75.00, 196.00, -76.74, 210.10, -79.00, 214.00, -79.00, 214.00, -73.96, 210.34, -73.22, 211.25, -77.00, 219.60, -81.40, 238.40, -81.40, 238.40, -67.64, 228.08, -66.39, 228.12, -71.40, 235.20, -81.40, 261.20, -81.40, 261.20, -67.93, 249.24, -67.39, 249.03, -69.00, 251.20, -72.20, 260.00, -72.20, 260.00, -53.39, 249.64, -48.97, 248.73, -49.31, 250.85, -59.80, 262.40, -59.80, 262.40, -52.31, 260.54, -47.40, 261.60, -47.40, 261.60, -41.92, 261.27, -41.40, 262.00, -41.40, 262.00, -49.70, 267.43, -57.61, 274.87, -62.93, 282.61, -65.80, 290.80, -65.80, 290.80, -61.30, 286.73, -59.99, 287.03, -60.60, 291.60, -60.20, 303.20, -60.20, 303.20, -58.30, 297.14, -57.37, 298.26, -56.60, 319.20, -56.60, 319.20, -48.49, 312.82, -45.76, 312.03, -45.35, 313.82, -49.00, 322.00, -49.00, 338.80, -49.00, 338.80, -40.26, 330.76, -38.63, 330.61, -40.20, 335.20, -40.20, 335.20, -36.26, 332.85, -34.22, 332.98, -33.27, 335.13, -34.20, 341.60, -34.20, 341.60, -33.94, 345.73, -33.17, 345.79, -30.60, 340.80, -30.60, 340.80, -22.95, 328.26, -20.19, 325.79, -19.29, 327.04, -20.60, 336.40, -20.60, 336.40, -20.14, 345.51, -19.15, 346.31, -16.60, 340.80, -16.60, 340.80, -15.40, 346.50, -12.14, 353.01, -7.00, 358.40, -7.00, 358.40, -6.27, 339.53, -4.46, 332.02, -2.77, 330.70, -0.27, 332.77, 4.60, 343.60, 8.60, 360.00, 8.60, 360.00, 10.64, 351.18, 11.00, 345.60, 19.00, 353.60, 19.00, 353.60, 28.79, 341.06, 31.17, 340.03, 31.00, 344.00, 31.00, 344.00, 25.45, 358.75, 25.00, 364.80, 25.00, 364.80, 43.00, 328.40, 43.00, 328.40, 43.39, 345.38, 44.82, 349.24, 46.77, 347.92, 51.80, 334.80, 51.80, 334.80, 54.49, 342.22, 55.39, 347.96, 54.60, 351.20, 54.60, 351.20, 60.76, 343.58, 61.80, 340.00, 61.80, 340.00, 64.54, 337.57, 66.77, 338.71, 69.20, 345.40, 69.20, 345.40, 71.39, 352.02, 72.60, 351.60, 72.60, 351.60, 75.37, 362.09, 76.42, 362.30, 77.80, 352.80, 77.80, 352.80, 77.75, 344.98, 75.91, 335.70, 72.20, 327.60, 72.20, 327.60, 72.12, 324.56, 70.20, 320.40, 70.20, 320.40, 75.70, 327.30, 77.91, 328.09, 78.69, 325.45, 76.60, 313.20, 76.60, 313.20, 89.00, 321.20, 89.00, 321.20, 82.70, 308.82, 81.23, 303.45, 81.82, 302.23, 84.20, 302.80, 84.20, 302.80, 83.54, 299.86, 84.53, 298.67, 87.95, 299.28, 97.00, 304.40, 97.00, 304.40, 91.03, 297.34, 90.41, 295.37, 91.64, 294.95, 98.60, 298.00, 98.60, 298.00, 101.93, 300.03, 102.23, 299.50, 99.00, 294.40, 99.00, 294.40, 94.40, 288.21, 95.27, 288.03, 106.60, 296.40, 106.60, 296.40, 116.61, 311.24, 119.00, 315.60, 119.00, 315.60, 108.86, 290.10, 104.60, 283.60, 104.60, 283.60, 108.02, 275.28, 114.09, 267.22, 121.76, 261.79, 130.82, 259.23, 141.00, 259.30, 154.20, 262.80, 154.20, 262.80, 157.75, 268.68, 160.36, 270.08, 162.73, 268.60, 165.40, 261.60, 165.40, 261.60, 170.08, 261.04, 176.25, 263.49, 182.75, 270.16, 189.40, 282.80, 189.40, 282.80, 192.36, 270.67, 192.60, 266.40, 192.60, 266.40, 198.19, 266.89, 198.60, 266.40, 198.60, 266.40, 210.19, 269.77, 213.00, 270.00, 213.00, 270.00, 217.51, 273.62, 219.47, 274.23, 220.20, 273.20, 220.20, 273.20, 225.75, 274.22, 227.51, 273.79, 227.40, 272.40, 227.40, 272.40, 234.76, 286.58, 236.60, 291.60, 239.00, 277.60, 241.00, 280.40, 241.00, 280.40, 242.01, 273.69, 241.80, 271.60, 241.80, 271.60, 242.83, 271.72, 249.79, 275.48, 257.44, 282.09, 263.14, 289.99, 266.60, 299.20, 268.60, 307.60, 268.60, 307.60, 272.87, 294.06, 273.00, 288.80, 273.00, 288.80, 277.01, 290.73, 278.60, 294.00, 278.60, 294.00, 280.13, 278.97, 279.59, 269.28, 277.80, 264.80, 277.80, 264.80, 281.47, 265.30, 283.40, 267.60, 283.40, 260.40, 283.40, 260.40, 289.30, 260.14, 290.60, 258.80, 290.60, 258.80, 293.21, 257.36, 295.38, 257.54, 297.00, 259.60, 297.00, 259.60, 293.38, 246.31, 293.06, 239.85, 294.31, 238.04, 296.82, 238.39, 303.00, 243.60, 303.00, 243.60, 306.29, 246.69, 307.45, 245.38, 306.60, 235.60, 306.60, 235.60, 302.46, 219.68, 301.73, 215.52, 303.80, 214.80, 303.80, 214.80, 303.85, 211.76, 302.60, 209.60, 302.60, 209.60, 301.93, 208.90, 303.80, 209.60, 303.80, 209.60, 305.11, 209.64, 305.81, 206.07, 303.40, 191.60, 303.40, 191.60, 304.82, 190.48, 304.47, 183.66, 297.80, 164.00, 297.80, 164.00, 298.73, 160.69, 296.60, 153.20, 296.60, 153.20, 303.95, 156.14, 307.40, 156.00, 307.40, 156.00, 307.15, 155.40, 303.80, 150.40, 303.80, 150.40, 295.80, 126.68, 293.83, 115.79, 294.65, 112.78, 296.76, 112.66, 302.60, 117.60, 302.60, 117.60, 307.07, 121.27, 309.11, 121.32, 309.97, 118.48, 308.05, 108.35, 308.05, 108.35, 300.79, 86.65, 299.72, 80.04, -129.83, 103.06, - }, 227); -} - -// Test points that are close to each other with higher precision. -test "Tiger whisker." { - try testLarge(&.{ -109.01000, 110.07000, -109.01000, 110.07000, -108.34000, 111.97000, -108.34000, 111.97000, -123.56751, 104.91863, -141.35422, 98.68091, -153.21875, 96.99554, -161.26077, 98.11440, -166.87000, 101.68000, -166.87000, 101.68000, -163.45908, 98.55489, -156.28145, 96.28941, -145.48354, 96.58372, -130.09291, 100.65533, -109.00999, 110.07000 }, 9); -} - -// Tests zig zag shape. -test "Tiger part." { - try testLarge(&.{ -54.20, 176.40, -54.20, 176.40, -51.54, 180.01, -50.04, 187.53, -51.51, 198.82, -57.40, 214.80, -51.00, 212.40, -51.00, 212.40, -52.75, 222.12, -55.00, 226.00, -47.80, 222.80, -47.80, 222.80, -45.85, 227.62, -45.49, 232.20, -47.00, 235.60, -47.00, 235.60, -37.04, 241.57, -32.01, 246.52, -31.00, 250.00, -31.00, 250.00, -28.25, 245.06, -27.27, 239.91, -28.60, 235.60, -28.60, 235.60, -32.06, 232.22, -36.59, 228.60, -38.53, 223.88, -39.00, 214.80, -47.80, 218.00, -47.80, 218.00, -43.55, 209.33, -42.20, 202.80, -50.20, 205.20, -50.20, 205.20, -43.67, 191.40, -41.68, 182.92, -42.47, 178.91, -45.40, 177.20, -45.40, 177.20, -53.55, 176.38, -54.20, 176.40 }, 29); -} - -test "Tiger whisker #2." { - try testLarge(&.{ 50.60, 84.00, 50.60, 84.00, 36.68, 72.16, 27.20, 65.81, 22.20, 64.00, 22.20, 64.00, 7.18, 63.89, -7.84, 66.44, -19.01, 71.16, -27.00, 78.00, -27.00, 78.00, -18.60, 70.90, -7.02, 64.99, 5.17, 62.33, 18.20, 63.20, 18.20, 63.20, 4.15, 61.23, -7.70, 60.89, -15.80, 62.00, -42.20, 76.00, -45.00, 80.80, -45.00, 80.80, -42.44, 75.17, -37.28, 68.75, -31.28, 64.00, -22.60, 60.00, -22.60, 60.00, -7.92, 58.05, 3.78, 58.19, 11.00, 60.00, 11.00, 60.00, -3.17, 56.40, -14.12, 54.88, -20.60, 55.20, -20.60, 55.20, -30.04, 55.69, -41.27, 58.57, -50.82, 63.73, -57.83, 70.06, -63.80, 79.20, -63.80, 79.20, -60.27, 71.59, -53.70, 63.52, -45.00, 57.60, -45.00, 57.60, -36.54, 53.82, -24.25, 51.26, -11.00, 51.60, -11.00, 51.60, 2.14, 54.95, 8.60, 57.20, 8.60, 57.20, 11.58, 58.03, 11.26, 56.98, 4.20, 52.00, 4.20, 52.00, 0.05, 47.36, -6.79, 43.57, -15.40, 42.40, -15.40, 42.40, -36.18, 45.32, -52.83, 49.44, -63.12, 53.75, -68.60, 58.00, -68.60, 58.00, -54.94, 48.57, -44.60, 44.00, -44.60, 44.00, -23.74, 37.92, -13.80, 36.80, -13.80, 36.80, 8.76, 36.15, 18.60, 33.80, 18.60, 33.80, 12.30, 37.54, 10.11, 40.15, 10.60, 42.00, 10.60, 42.00, 18.76, 51.11, 20.60, 54.00, 20.60, 54.00, 28.20, 61.89, 48.40, 81.70, 50.60, 84.00 }, 61); -} - -test "Tiger big part #2." { - try testLarge(&.{ 143.80, 259.60, 143.80, 259.60, 156.61, 257.34, 165.99, 254.14, 171.00, 250.80, 175.40, 254.40, 193.00, 216.00, 196.60, 221.20, 196.60, 221.20, 204.92, 211.13, 209.33, 203.27, 210.20, 198.40, 210.20, 198.40, 210.92, 196.01, 214.22, 196.97, 223.00, 204.40, 223.00, 204.40, 223.74, 198.82, 225.70, 197.45, 229.40, 199.60, 229.40, 199.60, 229.48, 191.67, 231.36, 189.69, 235.40, 192.00, 235.40, 192.00, 233.02, 182.21, 233.43, 178.04, 234.91, 177.19, 238.62, 178.91, 247.40, 187.60, 247.40, 187.60, 250.17, 190.36, 248.60, 187.20, 248.60, 187.20, 238.82, 166.34, 235.69, 155.55, 236.21, 151.81, 238.37, 150.90, 244.20, 153.60, 244.20, 153.60, 245.37, 133.23, 245.00, 126.40, 245.00, 126.40, 242.11, 109.37, 239.39, 98.81, 237.00, 94.40, 237.00, 94.40, 235.10, 91.16, 235.84, 89.80, 238.54, 89.93, 243.00, 92.80, 243.00, 92.80, 238.96, 81.92, 238.77, 78.05, 240.20, 77.49, 245.00, 80.80, 245.00, 80.80, 240.58, 67.71, 237.00, 62.80, 237.00, 62.80, 236.04, 56.18, 237.21, 53.39, 240.05, 52.95, 246.60, 56.40, 246.60, 56.40, 241.98, 45.41, 239.00, 40.80, 239.00, 40.80, 232.68, 22.48, 232.55, 17.75, 234.60, 18.00, 239.00, 21.60, 239.00, 21.60, 235.94, 13.32, 236.27, 11.21, 238.60, 12.00, 238.60, 12.00, 244.87, 15.98, 245.00, 16.00, 245.00, 16.00, 236.54, 0.65, 235.41, -4.10, 236.94, -4.65, 244.20, 0.40, 244.20, 0.40, 232.60, -20.40, 232.60, -20.40, 224.56, -30.47, 222.78, -34.51, 223.63, -35.63, 228.20, -34.40, 233.00, -32.80, 233.00, -32.80, 223.84, -40.87, 216.20, -44.40, 216.20, -44.40, 213.35, -45.94, 214.21, -47.98, 219.17, -50.41, 225.00, -50.40, 225.00, -50.40, 247.00, -40.80, 247.00, -40.80, 259.33, -24.93, 263.80, -21.60, 263.80, -21.60, 251.96, -24.82, 248.79, -24.16, 249.80, -21.20, 249.80, -21.20, 257.74, -11.96, 258.98, -8.44, 257.00, -7.60, 257.00, -7.60, 254.67, -3.13, 253.96, 2.58, 255.80, 8.40, 255.80, 8.40, 248.73, 2.56, 246.65, 2.15, 246.70, 4.49, 252.20, 15.60, 259.00, 32.00, 259.00, 32.00, 247.61, 21.93, 243.68, 20.11, 242.85, 21.48, 245.80, 29.20, 245.80, 29.20, 261.57, 49.78, 265.00, 53.20, 265.00, 53.20, 267.03, 55.03, 271.40, 62.40, 267.00, 60.40, 272.20, 69.20, 272.20, 69.20, 266.48, 64.35, 265.25, 64.72, 267.00, 70.40, 272.60, 84.80, 272.60, 84.80, 264.54, 77.74, 261.68, 77.09, 261.24, 79.97, 265.80, 92.40, 265.80, 92.40, 259.71, 91.77, 256.50, 93.34, 255.61, 96.92, 258.20, 104.40, 258.20, 104.40, 257.00, 125.60, 257.00, 125.60, 257.37, 146.74, 256.16, 160.73, 254.20, 167.20, 254.20, 167.20, 253.12, 172.65, 255.06, 182.19, 262.20, 198.40, 262.20, 198.40, 264.65, 206.19, 263.74, 207.59, 259.00, 204.00, 259.00, 204.00, 253.94, 199.29, 253.84, 200.28, 256.60, 209.20, 256.60, 209.20, 262.81, 231.18, 263.80, 239.20, 263.80, 239.20, 262.65, 239.33, 259.40, 236.80, 259.40, 236.80, 251.57, 226.52, 247.90, 223.71, 246.46, 224.22, 246.20, 228.40, 246.20, 228.40, 241.80, 245.20, 241.80, 245.20, 239.77, 250.25, 239.04, 250.56, 238.60, 247.20, 238.60, 247.20, 235.84, 237.79, 234.13, 236.00, 232.60, 238.00, 232.60, 238.00, 227.70, 248.46, 223.40, 254.00, 223.40, 254.00, 221.77, 253.32, 217.15, 243.71, 215.18, 241.23, 214.20, 244.00, 214.20, 244.00, 208.81, 240.31, 204.14, 239.64, 200.46, 241.80, 197.40, 248.00, 185.80, 264.40, 185.80, 264.40, 184.89, 256.37, 184.20, 258.00, 184.20, 258.00, 164.60, 260.78, 150.89, 261.06, 143.80, 259.60 }, 157); -} - -pub const DebugTriangulateStepResult = struct { - has_result: bool, - sweep_edges: []const SweepEdge, - event: Event, - out_verts: []const Vec2, - out_idxes: []const u16, - verts: []const InternalVertex, - deferred_verts: []const CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode).Node, - - pub fn deinit(self: @This(), alloc: std.mem.Allocator) void { - alloc.free(self.sweep_edges); - alloc.free(self.out_verts); - alloc.free(self.out_idxes); - alloc.free(self.verts); - alloc.free(self.deferred_verts); - } -}; - -// test "Simple breaking shape" { -// var polygon_buf = std.ArrayList(Vec2).init(t.allocator); -// defer polygon_buf.deinit(); -// try polygon_buf.appendSlice(&[_]Vec2{ -// Vec2{ 0, 0 }, -// Vec2{ 10, 0 }, -// Vec2{ 10, 10 }, -// Vec2{ 0, 10 }, - -// Vec2{ 0, 0 }, -// Vec2{ 0, 0 }, -// Vec2{ 2, 2 }, -// Vec2{ 8, 2 }, -// Vec2{ 8, 8 }, -// Vec2{ 2, 8 }, -// }); - -// var tessellator: Tessellator = undefined; -// tessellator.init(t.allocator); -// defer tessellator.deinit(); -// tessellator.triangulatePolygons(&.{ polygon_buf.items[0..5], polygon_buf.items[5..] }); -// // tessellator.triangulatePolygons(&.{polygon_buf.items[0..4]}); -// } diff --git a/examples/gkurve/tracy.zig b/examples/gkurve/tracy.zig deleted file mode 100644 index 00ff9fa8..00000000 --- a/examples/gkurve/tracy.zig +++ /dev/null @@ -1,314 +0,0 @@ -// Copied from zig/src/tracy.zig - -const std = @import("std"); -const builtin = @import("builtin"); -// TODO: integrate with tracy? -// const build_options = @import("build_options"); - -// pub const enable = if (builtin.is_test) false else build_options.enable_tracy; -// pub const enable_allocation = enable and build_options.enable_tracy_allocation; -// pub const enable_callstack = enable and build_options.enable_tracy_callstack; -pub const enable = false; -pub const enable_allocation = enable and false; -pub const enable_callstack = enable and false; - -// TODO: make this configurable -const callstack_depth = 10; - -const ___tracy_c_zone_context = extern struct { - id: u32, - active: c_int, - - pub inline fn end(self: @This()) void { - ___tracy_emit_zone_end(self); - } - - pub inline fn addText(self: @This(), text: []const u8) void { - ___tracy_emit_zone_text(self, text.ptr, text.len); - } - - pub inline fn setName(self: @This(), name: []const u8) void { - ___tracy_emit_zone_name(self, name.ptr, name.len); - } - - pub inline fn setColor(self: @This(), color: u32) void { - ___tracy_emit_zone_color(self, color); - } - - pub inline fn setValue(self: @This(), value: u64) void { - ___tracy_emit_zone_value(self, value); - } -}; - -pub const Ctx = if (enable) ___tracy_c_zone_context else struct { - pub inline fn end(self: @This()) void { - _ = self; - } - - pub inline fn addText(self: @This(), text: []const u8) void { - _ = self; - _ = text; - } - - pub inline fn setName(self: @This(), name: []const u8) void { - _ = self; - _ = name; - } - - pub inline fn setColor(self: @This(), color: u32) void { - _ = self; - _ = color; - } - - pub inline fn setValue(self: @This(), value: u64) void { - _ = self; - _ = value; - } -}; - -pub inline fn trace(comptime src: std.builtin.SourceLocation) Ctx { - if (!enable) return .{}; - - if (enable_callstack) { - return ___tracy_emit_zone_begin_callstack(&.{ - .name = null, - .function = src.fn_name.ptr, - .file = src.file.ptr, - .line = src.line, - .color = 0, - }, callstack_depth, 1); - } else { - return ___tracy_emit_zone_begin(&.{ - .name = null, - .function = src.fn_name.ptr, - .file = src.file.ptr, - .line = src.line, - .color = 0, - }, 1); - } -} - -pub inline fn traceNamed(comptime src: std.builtin.SourceLocation, comptime name: [:0]const u8) Ctx { - if (!enable) return .{}; - - if (enable_callstack) { - return ___tracy_emit_zone_begin_callstack(&.{ - .name = name.ptr, - .function = src.fn_name.ptr, - .file = src.file.ptr, - .line = src.line, - .color = 0, - }, callstack_depth, 1); - } else { - return ___tracy_emit_zone_begin(&.{ - .name = name.ptr, - .function = src.fn_name.ptr, - .file = src.file.ptr, - .line = src.line, - .color = 0, - }, 1); - } -} - -pub fn tracyAllocator(allocator: std.mem.Allocator) TracyAllocator(null) { - return TracyAllocator(null).init(allocator); -} - -pub fn TracyAllocator(comptime name: ?[:0]const u8) type { - return struct { - parent_allocator: std.mem.Allocator, - - const Self = @This(); - - pub fn init(parent_allocator: std.mem.Allocator) Self { - return .{ - .parent_allocator = parent_allocator, - }; - } - - pub fn allocator(self: *Self) std.mem.Allocator { - return std.mem.Allocator.init(self, allocFn, resizeFn, freeFn); - } - - fn allocFn(self: *Self, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) std.mem.Allocator.Error![]u8 { - const result = self.parent_allocator.rawAlloc(len, ptr_align, len_align, ret_addr); - if (result) |data| { - if (data.len != 0) { - if (name) |n| { - allocNamed(data.ptr, data.len, n); - } else { - alloc(data.ptr, data.len); - } - } - } else |_| { - messageColor("allocation failed", 0xFF0000); - } - return result; - } - - fn resizeFn(self: *Self, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) ?usize { - if (self.parent_allocator.rawResize(buf, buf_align, new_len, len_align, ret_addr)) |resized_len| { - if (name) |n| { - freeNamed(buf.ptr, n); - allocNamed(buf.ptr, resized_len, n); - } else { - free(buf.ptr); - alloc(buf.ptr, resized_len); - } - - return resized_len; - } - - // during normal operation the compiler hits this case thousands of times due to this - // emitting messages for it is both slow and causes clutter - return null; - } - - fn freeFn(self: *Self, buf: []u8, buf_align: u29, ret_addr: usize) void { - self.parent_allocator.rawFree(buf, buf_align, ret_addr); - // this condition is to handle free being called on an empty slice that was never even allocated - // example case: `std.process.getSelfExeSharedLibPaths` can return `&[_][:0]u8{}` - if (buf.len != 0) { - if (name) |n| { - freeNamed(buf.ptr, n); - } else { - free(buf.ptr); - } - } - } - }; -} - -// This function only accepts comptime known strings, see `messageCopy` for runtime strings -pub inline fn message(comptime msg: [:0]const u8) void { - if (!enable) return; - ___tracy_emit_messageL(msg.ptr, if (enable_callstack) callstack_depth else 0); -} - -// This function only accepts comptime known strings, see `messageColorCopy` for runtime strings -pub inline fn messageColor(comptime msg: [:0]const u8, color: u32) void { - if (!enable) return; - ___tracy_emit_messageLC(msg.ptr, color, if (enable_callstack) callstack_depth else 0); -} - -pub inline fn messageCopy(msg: []const u8) void { - if (!enable) return; - ___tracy_emit_message(msg.ptr, msg.len, if (enable_callstack) callstack_depth else 0); -} - -pub inline fn messageColorCopy(msg: [:0]const u8, color: u32) void { - if (!enable) return; - ___tracy_emit_messageC(msg.ptr, msg.len, color, if (enable_callstack) callstack_depth else 0); -} - -pub inline fn frameMark() void { - if (!enable) return; - ___tracy_emit_frame_mark(null); -} - -pub inline fn frameMarkNamed(comptime name: [:0]const u8) void { - if (!enable) return; - ___tracy_emit_frame_mark(name.ptr); -} - -pub inline fn namedFrame(comptime name: [:0]const u8) Frame(name) { - frameMarkStart(name); - return .{}; -} - -pub fn Frame(comptime name: [:0]const u8) type { - return struct { - pub fn end(_: @This()) void { - frameMarkEnd(name); - } - }; -} - -inline fn frameMarkStart(comptime name: [:0]const u8) void { - if (!enable) return; - ___tracy_emit_frame_mark_start(name.ptr); -} - -inline fn frameMarkEnd(comptime name: [:0]const u8) void { - if (!enable) return; - ___tracy_emit_frame_mark_end(name.ptr); -} - -extern fn ___tracy_emit_frame_mark_start(name: [*:0]const u8) void; -extern fn ___tracy_emit_frame_mark_end(name: [*:0]const u8) void; - -inline fn alloc(ptr: [*]u8, len: usize) void { - if (!enable) return; - - if (enable_callstack) { - ___tracy_emit_memory_alloc_callstack(ptr, len, callstack_depth, 0); - } else { - ___tracy_emit_memory_alloc(ptr, len, 0); - } -} - -inline fn allocNamed(ptr: [*]u8, len: usize, comptime name: [:0]const u8) void { - if (!enable) return; - - if (enable_callstack) { - ___tracy_emit_memory_alloc_callstack_named(ptr, len, callstack_depth, 0, name.ptr); - } else { - ___tracy_emit_memory_alloc_named(ptr, len, 0, name.ptr); - } -} - -inline fn free(ptr: [*]u8) void { - if (!enable) return; - - if (enable_callstack) { - ___tracy_emit_memory_free_callstack(ptr, callstack_depth, 0); - } else { - ___tracy_emit_memory_free(ptr, 0); - } -} - -inline fn freeNamed(ptr: [*]u8, comptime name: [:0]const u8) void { - if (!enable) return; - - if (enable_callstack) { - ___tracy_emit_memory_free_callstack_named(ptr, callstack_depth, 0, name.ptr); - } else { - ___tracy_emit_memory_free_named(ptr, 0, name.ptr); - } -} - -extern fn ___tracy_emit_zone_begin( - srcloc: *const ___tracy_source_location_data, - active: c_int, -) ___tracy_c_zone_context; -extern fn ___tracy_emit_zone_begin_callstack( - srcloc: *const ___tracy_source_location_data, - depth: c_int, - active: c_int, -) ___tracy_c_zone_context; -extern fn ___tracy_emit_zone_text(ctx: ___tracy_c_zone_context, txt: [*]const u8, size: usize) void; -extern fn ___tracy_emit_zone_name(ctx: ___tracy_c_zone_context, txt: [*]const u8, size: usize) void; -extern fn ___tracy_emit_zone_color(ctx: ___tracy_c_zone_context, color: u32) void; -extern fn ___tracy_emit_zone_value(ctx: ___tracy_c_zone_context, value: u64) void; -extern fn ___tracy_emit_zone_end(ctx: ___tracy_c_zone_context) void; -extern fn ___tracy_emit_memory_alloc(ptr: *const anyopaque, size: usize, secure: c_int) void; -extern fn ___tracy_emit_memory_alloc_callstack(ptr: *const anyopaque, size: usize, depth: c_int, secure: c_int) void; -extern fn ___tracy_emit_memory_free(ptr: *const anyopaque, secure: c_int) void; -extern fn ___tracy_emit_memory_free_callstack(ptr: *const anyopaque, depth: c_int, secure: c_int) void; -extern fn ___tracy_emit_memory_alloc_named(ptr: *const anyopaque, size: usize, secure: c_int, name: [*:0]const u8) void; -extern fn ___tracy_emit_memory_alloc_callstack_named(ptr: *const anyopaque, size: usize, depth: c_int, secure: c_int, name: [*:0]const u8) void; -extern fn ___tracy_emit_memory_free_named(ptr: *const anyopaque, secure: c_int, name: [*:0]const u8) void; -extern fn ___tracy_emit_memory_free_callstack_named(ptr: *const anyopaque, depth: c_int, secure: c_int, name: [*:0]const u8) void; -extern fn ___tracy_emit_message(txt: [*]const u8, size: usize, callstack: c_int) void; -extern fn ___tracy_emit_messageL(txt: [*:0]const u8, callstack: c_int) void; -extern fn ___tracy_emit_messageC(txt: [*]const u8, size: usize, color: u32, callstack: c_int) void; -extern fn ___tracy_emit_messageLC(txt: [*:0]const u8, color: u32, callstack: c_int) void; -extern fn ___tracy_emit_frame_mark(name: ?[*:0]const u8) void; - -const ___tracy_source_location_data = extern struct { - name: ?[*:0]const u8, - function: [*:0]const u8, - file: [*:0]const u8, - line: u32, - color: u32, -}; diff --git a/examples/gkurve/vert.wgsl b/examples/gkurve/vert.wgsl deleted file mode 100644 index 94d3ac91..00000000 --- a/examples/gkurve/vert.wgsl +++ /dev/null @@ -1,40 +0,0 @@ -struct VertexUniform { - matrix: mat4x4, -} -@binding(0) @group(0) var ubo: VertexUniform; - -struct VertexOut { - @builtin(position) position_clip: vec4, - @location(0) frag_uv: vec2, - @interpolate(linear) @location(1) frag_bary: vec2, - @interpolate(flat) @location(2) triangle_index: u32, -} - -@vertex fn main( - @builtin(vertex_index) vertex_index: u32, - @location(0) position: vec4, - @location(1) uv: vec2, -) -> VertexOut { - var output : VertexOut; - output.position_clip = ubo.matrix * position; - output.frag_uv = uv; - - // Generates [0.0, 0.0], [0.5, 0.0], [1.0, 1.0] - // - // Equal to: - // - // if ((vertex_index+1u) % 3u == 0u) { - // output.frag_bary = vec2(0.0, 0.0); - // } else if ((vertex_index+1u) % 3u == 1u) { - // output.frag_bary = vec2(0.5, 0.0); - // } else { - // output.frag_bary = vec2(1.0, 1.0); - // } - // - output.frag_bary = vec2( - f32((vertex_index+1u) % 3u) * 0.5, - 1.0 - f32((((vertex_index + 3u) % 3u) + 1u) % 2u), - ); - output.triangle_index = vertex_index / 3u; - return output; -} diff --git a/examples/image-blur/assets b/examples/image-blur/assets deleted file mode 160000 index b5a84047..00000000 --- a/examples/image-blur/assets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b5a8404715e6cfc57e66c4a7cc0625e64b3b3c56 diff --git a/examples/image-blur/blur.wgsl b/examples/image-blur/blur.wgsl deleted file mode 100644 index bd913602..00000000 --- a/examples/image-blur/blur.wgsl +++ /dev/null @@ -1,83 +0,0 @@ -struct Params { - filterDim : u32, - blockDim : u32, -} - -@group(0) @binding(0) var samp : sampler; -@group(0) @binding(1) var params : Params; -@group(1) @binding(1) var inputTex : texture_2d; -@group(1) @binding(2) var outputTex : texture_storage_2d; - -struct Flip { - value : u32, -} -@group(1) @binding(3) var flip : Flip; - -// This shader blurs the input texture in one direction, depending on whether -// |flip.value| is 0 or 1. -// It does so by running (128 / 4) threads per workgroup to load 128 -// texels into 4 rows of shared memory. Each thread loads a -// 4 x 4 block of texels to take advantage of the texture sampling -// hardware. -// Then, each thread computes the blur result by averaging the adjacent texel values -// in shared memory. -// Because we're operating on a subset of the texture, we cannot compute all of the -// results since not all of the neighbors are available in shared memory. -// Specifically, with 128 x 128 tiles, we can only compute and write out -// square blocks of size 128 - (filterSize - 1). We compute the number of blocks -// needed in Javascript and dispatch that amount. - -var tile : array, 128>, 4>; - -@compute @workgroup_size(32, 1, 1) -fn main( - @builtin(workgroup_id) WorkGroupID : vec3, - @builtin(local_invocation_id) LocalInvocationID : vec3 -) { - let filterOffset : u32 = (params.filterDim - 1u) / 2u; - let dims : vec2 = textureDimensions(inputTex, 0); - - let baseIndex = vec2( - WorkGroupID.xy * vec2(params.blockDim, 4u) + - LocalInvocationID.xy * vec2(4u, 1u) - ) - vec2(i32(filterOffset), 0); - - for (var r : u32 = 0u; r < 4u; r = r + 1u) { - for (var c : u32 = 0u; c < 4u; c = c + 1u) { - var loadIndex = baseIndex + vec2(i32(c), i32(r)); - if (flip.value != 0u) { - loadIndex = loadIndex.yx; - } - - tile[r][4u * LocalInvocationID.x + c] = textureSampleLevel( - inputTex, - samp, - (vec2(loadIndex) + vec2(0.25, 0.25)) / vec2(dims), - 0.0 - ).rgb; - } - } - - workgroupBarrier(); - - for (var r : u32 = 0u; r < 4u; r = r + 1u) { - for (var c : u32 = 0u; c < 4u; c = c + 1u) { - var writeIndex = baseIndex + vec2(i32(c), i32(r)); - if (flip.value != 0u) { - writeIndex = writeIndex.yx; - } - - let center : u32 = 4u * LocalInvocationID.x + c; - if (center >= filterOffset && - center < 128u - filterOffset && - all(writeIndex < dims)) { - var acc : vec3 = vec3(0.0, 0.0, 0.0); - for (var f : u32 = 0u; f < params.filterDim; f = f + 1u) { - var i : u32 = center + f - filterOffset; - acc = acc + (1.0 / f32(params.filterDim)) * tile[r][i]; - } - textureStore(outputTex, writeIndex, vec4(acc, 1.0)); - } - } - } -} diff --git a/examples/image-blur/fullscreen_textured_quad.wgsl b/examples/image-blur/fullscreen_textured_quad.wgsl deleted file mode 100644 index 61c461c0..00000000 --- a/examples/image-blur/fullscreen_textured_quad.wgsl +++ /dev/null @@ -1,38 +0,0 @@ -@group(0) @binding(0) var mySampler : sampler; -@group(0) @binding(1) var myTexture : texture_2d; - -struct VertexOutput { - @builtin(position) Position : vec4, - @location(0) fragUV : vec2, -} - -@vertex -fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput { - var pos = array, 6>( - vec2( 1.0, 1.0), - vec2( 1.0, -1.0), - vec2(-1.0, -1.0), - vec2( 1.0, 1.0), - vec2(-1.0, -1.0), - vec2(-1.0, 1.0) - ); - - var uv = array, 6>( - vec2(1.0, 0.0), - vec2(1.0, 1.0), - vec2(0.0, 1.0), - vec2(1.0, 0.0), - vec2(0.0, 1.0), - vec2(0.0, 0.0) - ); - - var output : VertexOutput; - output.Position = vec4(pos[VertexIndex], 0.0, 1.0); - output.fragUV = uv[VertexIndex]; - return output; -} - -@fragment -fn frag_main(@location(0) fragUV : vec2) -> @location(0) vec4 { - return textureSample(myTexture, mySampler, fragUV); -} diff --git a/examples/image-blur/main.zig b/examples/image-blur/main.zig deleted file mode 100644 index 5ea520f4..00000000 --- a/examples/image-blur/main.zig +++ /dev/null @@ -1,267 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const zigimg = @import("zigimg"); - -queue: *gpu.Queue, -blur_pipeline: *gpu.ComputePipeline, -fullscreen_quad_pipeline: *gpu.RenderPipeline, -cube_texture: *gpu.Texture, -textures: [2]*gpu.Texture, -blur_params_buffer: *gpu.Buffer, -compute_constants: *gpu.BindGroup, -compute_bind_group_0: *gpu.BindGroup, -compute_bind_group_1: *gpu.BindGroup, -compute_bind_group_2: *gpu.BindGroup, -show_result_bind_group: *gpu.BindGroup, -img_size: gpu.Extent3D, - -pub const App = @This(); - -// Constants from the blur.wgsl shader -const tile_dimension: u32 = 128; -const batch: [2]u32 = .{ 4, 4 }; - -// Currently hardcoded -const filter_size: u32 = 15; -const iterations: u32 = 2; -var block_dimension: u32 = tile_dimension - (filter_size - 1); - -pub fn init(app: *App, core: *mach.Core) !void { - const queue = core.device.getQueue(); - - const blur_shader_module = core.device.createShaderModuleWGSL("blur.wgsl", @embedFile("blur.wgsl")); - - const blur_pipeline_descriptor = gpu.ComputePipeline.Descriptor{ - .compute = gpu.ProgrammableStageDescriptor{ - .module = blur_shader_module, - .entry_point = "main", - }, - }; - - const blur_pipeline = core.device.createComputePipeline(&blur_pipeline_descriptor); - - const fullscreen_quad_vs_module = core.device.createShaderModuleWGSL( - "fullscreen_textured_quad.wgsl", - @embedFile("fullscreen_textured_quad.wgsl"), - ); - - const fullscreen_quad_fs_module = core.device.createShaderModuleWGSL( - "fullscreen_textured_quad.wgsl", - @embedFile("fullscreen_textured_quad.wgsl"), - ); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - - const fragment_state = gpu.FragmentState.init(.{ - .module = fullscreen_quad_fs_module, - .entry_point = "frag_main", - .targets = &.{color_target}, - }); - - const fullscreen_quad_pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment_state, - .vertex = .{ - .module = fullscreen_quad_vs_module, - .entry_point = "vert_main", - }, - }; - - const fullscreen_quad_pipeline = core.device.createRenderPipeline(&fullscreen_quad_pipeline_descriptor); - - const sampler = core.device.createSampler(&.{ - .mag_filter = .linear, - .min_filter = .linear, - }); - - var img = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/gotta-go-fast.png")); - defer img.deinit(); - - const img_size = gpu.Extent3D{ .width = @intCast(u32, img.width), .height = @intCast(u32, img.height) }; - - const cube_texture = core.device.createTexture(&.{ - .size = img_size, - .format = .rgba8_unorm, - .usage = .{ - .texture_binding = true, - .copy_dst = true, - .render_attachment = true, - }, - }); - - const data_layout = gpu.Texture.DataLayout{ - .bytes_per_row = @intCast(u32, img.width * 4), - .rows_per_image = @intCast(u32, img.height), - }; - - switch (img.pixels) { - .rgba32 => |pixels| queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, pixels), - .rgb24 => |pixels| { - const data = try rgb24ToRgba32(core.allocator, pixels); - defer data.deinit(core.allocator); - queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, data.rgba32); - }, - else => @panic("unsupported image color format"), - } - - var textures: [2]*gpu.Texture = undefined; - for (textures) |_, i| { - textures[i] = core.device.createTexture(&.{ - .size = img_size, - .format = .rgba8_unorm, - .usage = .{ - .storage_binding = true, - .texture_binding = true, - .copy_dst = true, - }, - }); - } - - // the shader blurs the input texture in one direction, - // depending on whether flip value is 0 or 1 - var flip: [2]*gpu.Buffer = undefined; - for (flip) |_, i| { - const buffer = core.device.createBuffer(&.{ - .usage = .{ .uniform = true }, - .size = @sizeOf(u32), - .mapped_at_creation = true, - }); - - const buffer_mapped = buffer.getMappedRange(u32, 0, 1); - buffer_mapped.?[0] = @intCast(u32, i); - buffer.unmap(); - - flip[i] = buffer; - } - - const blur_params_buffer = core.device.createBuffer(&.{ - .size = 8, - .usage = .{ .copy_dst = true, .uniform = true }, - }); - - const compute_constants = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = blur_pipeline.getBindGroupLayout(0), - .entries = &.{ - gpu.BindGroup.Entry.sampler(0, sampler), - gpu.BindGroup.Entry.buffer(1, blur_params_buffer, 0, 8), - }, - })); - - const compute_bind_group_0 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = blur_pipeline.getBindGroupLayout(1), - .entries = &.{ - gpu.BindGroup.Entry.textureView(1, cube_texture.createView(&gpu.TextureView.Descriptor{})), - gpu.BindGroup.Entry.textureView(2, textures[0].createView(&gpu.TextureView.Descriptor{})), - gpu.BindGroup.Entry.buffer(3, flip[0], 0, 4), - }, - })); - - const compute_bind_group_1 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = blur_pipeline.getBindGroupLayout(1), - .entries = &.{ - gpu.BindGroup.Entry.textureView(1, textures[0].createView(&gpu.TextureView.Descriptor{})), - gpu.BindGroup.Entry.textureView(2, textures[1].createView(&gpu.TextureView.Descriptor{})), - gpu.BindGroup.Entry.buffer(3, flip[1], 0, 4), - }, - })); - - const compute_bind_group_2 = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = blur_pipeline.getBindGroupLayout(1), - .entries = &.{ - gpu.BindGroup.Entry.textureView(1, textures[1].createView(&gpu.TextureView.Descriptor{})), - gpu.BindGroup.Entry.textureView(2, textures[0].createView(&gpu.TextureView.Descriptor{})), - gpu.BindGroup.Entry.buffer(3, flip[0], 0, 4), - }, - })); - - const show_result_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = fullscreen_quad_pipeline.getBindGroupLayout(0), - .entries = &.{ - gpu.BindGroup.Entry.sampler(0, sampler), - gpu.BindGroup.Entry.textureView(1, textures[1].createView(&gpu.TextureView.Descriptor{})), - }, - })); - - const blur_params_buffer_data = [_]u32{ filter_size, block_dimension }; - queue.writeBuffer(blur_params_buffer, 0, &blur_params_buffer_data); - - app.queue = queue; - app.blur_pipeline = blur_pipeline; - app.fullscreen_quad_pipeline = fullscreen_quad_pipeline; - app.cube_texture = cube_texture; - app.textures = textures; - app.blur_params_buffer = blur_params_buffer; - app.compute_constants = compute_constants; - app.compute_bind_group_0 = compute_bind_group_0; - app.compute_bind_group_1 = compute_bind_group_1; - app.compute_bind_group_2 = compute_bind_group_2; - app.show_result_bind_group = show_result_bind_group; - app.img_size = img_size; -} - -pub fn deinit(_: *App, _: *mach.Core) void {} - -pub fn update(app: *App, core: *mach.Core) !void { - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const encoder = core.device.createCommandEncoder(null); - - const compute_pass = encoder.beginComputePass(null); - compute_pass.setPipeline(app.blur_pipeline); - compute_pass.setBindGroup(0, app.compute_constants, &.{}); - - const width: u32 = @intCast(u32, app.img_size.width); - const height: u32 = @intCast(u32, app.img_size.height); - compute_pass.setBindGroup(1, app.compute_bind_group_0, &.{}); - compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, width, block_dimension), try std.math.divCeil(u32, height, batch[1]), 1); - - compute_pass.setBindGroup(1, app.compute_bind_group_1, &.{}); - compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, height, block_dimension), try std.math.divCeil(u32, width, batch[1]), 1); - - var i: u32 = 0; - while (i < iterations - 1) : (i += 1) { - compute_pass.setBindGroup(1, app.compute_bind_group_2, &.{}); - compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, width, block_dimension), try std.math.divCeil(u32, height, batch[1]), 1); - - compute_pass.setBindGroup(1, app.compute_bind_group_1, &.{}); - compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, height, block_dimension), try std.math.divCeil(u32, width, batch[1]), 1); - } - compute_pass.end(); - - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const render_pass_descriptor = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - - const render_pass = encoder.beginRenderPass(&render_pass_descriptor); - render_pass.setPipeline(app.fullscreen_quad_pipeline); - render_pass.setBindGroup(0, app.show_result_bind_group, &.{}); - render_pass.draw(6, 1, 0, 0); - render_pass.end(); - - var command = encoder.finish(null); - encoder.release(); - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} - -fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage { - const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len); - var i: usize = 0; - while (i < in.len) : (i += 1) { - out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 }; - } - return out; -} diff --git a/examples/instanced-cube/cube_mesh.zig b/examples/instanced-cube/cube_mesh.zig deleted file mode 100644 index f26c75ac..00000000 --- a/examples/instanced-cube/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(4, f32), - col: @Vector(4, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, -}; diff --git a/examples/instanced-cube/frag.wgsl b/examples/instanced-cube/frag.wgsl deleted file mode 100755 index 15ed50c9..00000000 --- a/examples/instanced-cube/frag.wgsl +++ /dev/null @@ -1,6 +0,0 @@ -@fragment fn main( - @location(0) fragUV: vec2, - @location(1) fragPosition: vec4 -) -> @location(0) vec4 { - return fragPosition; -} diff --git a/examples/instanced-cube/main.zig b/examples/instanced-cube/main.zig deleted file mode 100755 index bdab745c..00000000 --- a/examples/instanced-cube/main.zig +++ /dev/null @@ -1,189 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; - -const UniformBufferObject = struct { - mat: zm.Mat, -}; - -var timer: mach.Timer = undefined; - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, - -pub const App = @This(); - -pub fn init(app: *App, core: *mach.Core) !void { - timer = try mach.Timer.start(); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{bgle}, - }), - ); - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }), - .primitive = .{ - .cull_mode = .back, - }, - }; - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - const x_count = 4; - const y_count = 4; - const num_instances = x_count * y_count; - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject) * num_instances, - .mapped_at_creation = false, - }); - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject) * num_instances), - }, - }), - ); - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = core.device.getQueue(); - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group = bind_group; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); - bgl.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.vertex_buffer.release(); - app.bind_group.release(); - app.uniform_buffer.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - - { - const proj = zm.perspectiveFovRh( - (std.math.pi / 3.0), - @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height), - 10, - 30, - ); - - var ubos: [16]UniformBufferObject = undefined; - const time = timer.read(); - const step: f32 = 4.0; - var m: u8 = 0; - var x: u8 = 0; - while (x < 4) : (x += 1) { - var y: u8 = 0; - while (y < 4) : (y += 1) { - const trans = zm.translation(step * (@intToFloat(f32, x) - 2.0 + 0.5), step * (@intToFloat(f32, y) - 2.0 + 0.5), -20); - const localTime = time + @intToFloat(f32, m) * 0.5; - const model = zm.mul(zm.mul(zm.mul(zm.rotationX(localTime * (std.math.pi / 2.1)), zm.rotationY(localTime * (std.math.pi / 0.9))), zm.rotationZ(localTime * (std.math.pi / 1.3))), trans); - const mvp = zm.mul(model, proj); - const ubo = UniformBufferObject{ - .mat = mvp, - }; - ubos[m] = ubo; - m += 1; - } - } - encoder.writeBuffer(app.uniform_buffer, 0, &ubos); - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - pass.setBindGroup(0, app.bind_group, &.{0}); - pass.draw(vertices.len, 16, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} diff --git a/examples/instanced-cube/vert.wgsl b/examples/instanced-cube/vert.wgsl deleted file mode 100644 index a5c38e9c..00000000 --- a/examples/instanced-cube/vert.wgsl +++ /dev/null @@ -1,18 +0,0 @@ -@binding(0) @group(0) var ubos : array, 16>; - -struct VertexOutput { - @builtin(position) position_clip : vec4, - @location(0) fragUV : vec2, - @location(1) fragPosition: vec4, -}; - -@vertex -fn main(@builtin(instance_index) instanceIdx : u32, - @location(0) position : vec4, - @location(1) uv : vec2) -> VertexOutput { - var output : VertexOutput; - output.position_clip = ubos[instanceIdx] * position; - output.fragUV = uv; - output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); - return output; -} \ No newline at end of file diff --git a/examples/libs/zigimg b/examples/libs/zigimg deleted file mode 160000 index fff6ea92..00000000 --- a/examples/libs/zigimg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fff6ea92a00c5f6092b896d754a932b8b88149ff diff --git a/examples/libs/zmath b/examples/libs/zmath deleted file mode 160000 index c7f20369..00000000 --- a/examples/libs/zmath +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c7f20369a142c8a817587da529787597461410a5 diff --git a/examples/map-async/main.wgsl b/examples/map-async/main.wgsl deleted file mode 100644 index a4270992..00000000 --- a/examples/map-async/main.wgsl +++ /dev/null @@ -1,16 +0,0 @@ -@group(0) @binding(0) var output: array; - -@compute @workgroup_size(64, 1, 1) -fn main( - @builtin(global_invocation_id) - global_id : vec3, - - @builtin(local_invocation_id) - local_id : vec3, -) { - if (global_id.x >= arrayLength(&output)) { - return; - } - output[global_id.x] = - f32(global_id.x) * 1000. + f32(local_id.x); -} diff --git a/examples/map-async/main.zig b/examples/map-async/main.zig deleted file mode 100644 index 55137073..00000000 --- a/examples/map-async/main.zig +++ /dev/null @@ -1,86 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); - -pub const App = @This(); - -// TODO(self-hosted): https://github.com/ziglang/zig/issues/12275 -_unused: i32, - -const workgroup_size = 64; -const buffer_size = 1000; - -pub fn init(_: *App, core: *mach.Core) !void { - const output = core.device.createBuffer(&.{ - .usage = .{ .storage = true, .copy_src = true }, - .size = buffer_size * @sizeOf(f32), - .mapped_at_creation = false, - }); - - const staging = core.device.createBuffer(&.{ - .usage = .{ .map_read = true, .copy_dst = true }, - .size = buffer_size * @sizeOf(f32), - .mapped_at_creation = false, - }); - - const compute_module = core.device.createShaderModuleWGSL("main.wgsl", @embedFile("main.wgsl")); - - const compute_pipeline = core.device.createComputePipeline(&gpu.ComputePipeline.Descriptor{ .compute = gpu.ProgrammableStageDescriptor{ - .module = compute_module, - .entry_point = "main", - } }); - - const compute_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ - .layout = compute_pipeline.getBindGroupLayout(0), - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, output, 0, buffer_size), - }, - })); - - compute_module.release(); - - const encoder = core.device.createCommandEncoder(null); - - const compute_pass = encoder.beginComputePass(null); - compute_pass.setPipeline(compute_pipeline); - compute_pass.setBindGroup(0, compute_bind_group, &.{}); - compute_pass.dispatchWorkgroups(try std.math.divCeil(u32, buffer_size, workgroup_size), 1, 1); - compute_pass.end(); - - encoder.copyBufferToBuffer(output, 0, staging, 0, buffer_size); - - var command = encoder.finish(null); - encoder.release(); - - var response: gpu.Buffer.MapAsyncStatus = undefined; - const callback = (struct { - pub inline fn callback(ctx: *gpu.Buffer.MapAsyncStatus, status: gpu.Buffer.MapAsyncStatus) void { - ctx.* = status; - } - }).callback; - - var queue = core.device.getQueue(); - queue.submit(&.{command}); - - staging.mapAsync(.{ .read = true }, 0, buffer_size, &response, callback); - while (true) { - if (response == gpu.Buffer.MapAsyncStatus.success) { - break; - } else { - core.device.tick(); - } - } - - const staging_mapped = staging.getConstMappedRange(f32, 0, buffer_size / @sizeOf(f32)); - for (staging_mapped.?) |v| { - std.debug.print("{d} ", .{v}); - } - std.debug.print("\n", .{}); - staging.unmap(); -} - -pub fn deinit(_: *App, _: *mach.Core) void {} - -pub fn update(_: *App, core: *mach.Core) !void { - core.close(); -} diff --git a/examples/pixel-post-process/cube_mesh.zig b/examples/pixel-post-process/cube_mesh.zig deleted file mode 100644 index edf25840..00000000 --- a/examples/pixel-post-process/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(3, f32), - normal: @Vector(3, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, -1, 0 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1 }, .normal = .{ 1, 0, 0 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 1, 0 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, -1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1 }, .normal = .{ -1, 0, 0 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1 }, .normal = .{ 0, 0, 1 }, .uv = .{ 1, 1 } }, - - .{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1 }, .normal = .{ 0, 0, -1 }, .uv = .{ 0, 0 } }, -}; diff --git a/examples/pixel-post-process/frag.wgsl b/examples/pixel-post-process/frag.wgsl deleted file mode 100644 index 0a0cc2d5..00000000 --- a/examples/pixel-post-process/frag.wgsl +++ /dev/null @@ -1,8 +0,0 @@ -@fragment fn main( - @location(0) normal: vec3, - @location(1) uv: vec2, -) -> @location(0) vec4 { - var color = floor((uv * 0.5 + 0.25) * 32) / 32; - return vec4(color, 1, 1); -} - diff --git a/examples/pixel-post-process/main.zig b/examples/pixel-post-process/main.zig deleted file mode 100644 index 6e602a51..00000000 --- a/examples/pixel-post-process/main.zig +++ /dev/null @@ -1,435 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); - -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; -const Quad = @import("quad_mesh.zig").Quad; -const quad = @import("quad_mesh.zig").quad; - -pub const App = @This(); - -const pixel_size = 8; - -const UniformBufferObject = struct { - mat: zm.Mat, -}; -const PostUniformBufferObject = extern struct { - width: u32, - height: u32, - pixel_size: u32 = pixel_size, -}; - -timer: mach.Timer = undefined, -queue: *gpu.Queue, - -pipeline: *gpu.RenderPipeline, -normal_pipeline: *gpu.RenderPipeline, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, - -post_pipeline: *gpu.RenderPipeline, -post_vertex_buffer: *gpu.Buffer, -post_uniform_buffer: *gpu.Buffer, -post_bind_group: *gpu.BindGroup, - -draw_texture_view: *gpu.TextureView, -depth_texture_view: *gpu.TextureView, -normal_texture_view: *gpu.TextureView, - -pub fn init(app: *App, core: *mach.Core) !void { - app.timer = try mach.Timer.start(); - - app.create_render_textures(core); - app.create_draw_pipeline(core); - app.create_post_pipeline(core); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.cleanup(); -} - -pub fn resize(app: *App, core: *mach.Core, _: u32, _: u32) !void { - app.cleanup(); - app.create_render_textures(core); - app.create_draw_pipeline(core); - app.create_post_pipeline(core); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const encoder = core.device.createCommandEncoder(null); - encoder.writeBuffer(app.post_uniform_buffer, 0, &[_]PostUniformBufferObject{ - PostUniformBufferObject{ - .width = core.getWindowSize().width, - .height = core.getWindowSize().height, - }, - }); - - { - const time = app.timer.read() * 0.5; - const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0))); - const view = zm.lookAtRh( - zm.f32x4(0, 5, 2, 1), - zm.f32x4(0, 0, 0, 1), - zm.f32x4(0, 0, 1, 0), - ); - const proj = zm.perspectiveFovRh( - (std.math.pi / 4.0), - @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height), - 0.1, - 10, - ); - const mvp = zm.mul(zm.mul(model, view), proj); - const ubo = UniformBufferObject{ - .mat = zm.transpose(mvp), - }; - encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo}); - } - - { - // render scene to downscaled texture - const color_attachment = gpu.RenderPassColorAttachment{ - .view = app.draw_texture_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - .depth_stencil_attachment = &gpu.RenderPassDepthStencilAttachment{ - .view = app.depth_texture_view, - .depth_load_op = .clear, - .depth_store_op = .store, - .depth_clear_value = 1.0, - }, - }); - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - pass.setBindGroup(0, app.bind_group, &.{0}); - pass.draw(vertices.len, 1, 0, 0); - pass.end(); - pass.release(); - } - - { - // render scene normals to texture - const normal_color_attachment = gpu.RenderPassColorAttachment{ - .view = app.normal_texture_view, - .clear_value = .{ .r = 0.5, .b = 0.5, .g = 0.5, .a = 1.0 }, - .load_op = .clear, - .store_op = .store, - }; - const normal_render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{normal_color_attachment}, - }); - - const normal_pass = encoder.beginRenderPass(&normal_render_pass_info); - normal_pass.setPipeline(app.normal_pipeline); - normal_pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - normal_pass.setBindGroup(0, app.bind_group, &.{0}); - normal_pass.draw(vertices.len, 1, 0, 0); - normal_pass.end(); - normal_pass.release(); - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - { - // render to swap_chain using previous passes - const post_color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - const post_render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{post_color_attachment}, - }); - - const draw_pass = encoder.beginRenderPass(&post_render_pass_info); - draw_pass.setPipeline(app.post_pipeline); - draw_pass.setVertexBuffer(0, app.post_vertex_buffer, 0, @sizeOf(Quad) * quad.len); - draw_pass.setBindGroup(0, app.post_bind_group, &.{0}); - draw_pass.draw(quad.len, 1, 0, 0); - draw_pass.end(); - draw_pass.release(); - } - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} - -fn cleanup(app: *App) void { - app.vertex_buffer.release(); - app.uniform_buffer.release(); - app.bind_group.release(); - - app.post_vertex_buffer.release(); - app.post_uniform_buffer.release(); - app.post_bind_group.release(); - - app.draw_texture_view.release(); - app.depth_texture_view.release(); - app.normal_texture_view.release(); -} - -fn create_render_textures(app: *App, core: *mach.Core) void { - const draw_texture_desc = gpu.Texture.Descriptor.init(.{ - .size = .{ .width = core.getWindowSize().width / pixel_size, .height = core.getWindowSize().height / pixel_size }, - .format = .bgra8_unorm, - .usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true }, - }); - const draw_texture = core.device.createTexture(&draw_texture_desc); - app.draw_texture_view = draw_texture.createView(null); - - const depth_texture_desc = gpu.Texture.Descriptor.init(.{ - .size = .{ .width = core.getWindowSize().width / pixel_size, .height = core.getWindowSize().height / pixel_size }, - .format = .depth32_float, - .usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true }, - }); - const depth_texture = core.device.createTexture(&depth_texture_desc); - app.depth_texture_view = depth_texture.createView(null); - - const normal_texture_desc = gpu.Texture.Descriptor.init(.{ - .size = .{ .width = core.getWindowSize().width / pixel_size, .height = core.getWindowSize().height / pixel_size }, - .format = .bgra8_unorm, - .usage = .{ .texture_binding = true, .copy_dst = true, .render_attachment = true }, - }); - const normal_texture = core.device.createTexture(&normal_texture_desc); - app.normal_texture_view = normal_texture.createView(null); -} - -fn create_draw_pipeline(app: *App, core: *mach.Core) void { - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x3, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x3, .offset = @offsetOf(Vertex, "normal"), .shader_location = 1 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 2 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - const vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{bgle}, - }), - ); - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout( - &gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - }), - ); - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject), - .mapped_at_creation = false, - }); - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - }, - }), - ); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = vertex, - .primitive = .{ - .cull_mode = .back, - }, - .depth_stencil = &gpu.DepthStencilState{ - .format = .depth32_float, - .depth_write_enabled = true, - .depth_compare = .less, - }, - }; - - { - // "same" pipeline, different fragment shader to create a texture with normal information - const normal_fs_module = core.device.createShaderModuleWGSL("normal_frag.wgsl", @embedFile("normal_frag.wgsl")); - const normal_fragment = gpu.FragmentState.init(.{ - .module = normal_fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - const normal_pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &normal_fragment, - .layout = pipeline_layout, - .vertex = vertex, - .primitive = .{ - .cull_mode = .back, - }, - }; - app.normal_pipeline = core.device.createRenderPipeline(&normal_pipeline_descriptor); - - normal_fs_module.release(); - } - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = core.device.getQueue(); - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group = bind_group; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); - bgl.release(); -} - -fn create_post_pipeline(app: *App, core: *mach.Core) void { - const vs_module = core.device.createShaderModuleWGSL("pixel_vert.wgsl", @embedFile("pixel_vert.wgsl")); - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x3, .offset = @offsetOf(Quad, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Quad, "uv"), .shader_location = 1 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Quad), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - const vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }); - - const fs_module = core.device.createShaderModuleWGSL("pixel_frag.wgsl", @embedFile("pixel_frag.wgsl")); - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{ - gpu.BindGroupLayout.Entry.texture(0, .{ .fragment = true }, .float, .dimension_2d, false), - gpu.BindGroupLayout.Entry.sampler(1, .{ .fragment = true }, .filtering), - gpu.BindGroupLayout.Entry.texture(2, .{ .fragment = true }, .depth, .dimension_2d, false), - gpu.BindGroupLayout.Entry.sampler(3, .{ .fragment = true }, .filtering), - gpu.BindGroupLayout.Entry.texture(4, .{ .fragment = true }, .float, .dimension_2d, false), - gpu.BindGroupLayout.Entry.sampler(5, .{ .fragment = true }, .filtering), - gpu.BindGroupLayout.Entry.buffer(6, .{ .fragment = true }, .uniform, true, 0), - }, - }), - ); - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Quad) * quad.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Quad, 0, quad.len); - std.mem.copy(Quad, vertex_mapped.?, quad[0..]); - vertex_buffer.unmap(); - - const draw_sampler = core.device.createSampler(null); - const depth_sampler = core.device.createSampler(null); - const normal_sampler = core.device.createSampler(null); - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(PostUniformBufferObject), - .mapped_at_creation = false, - }); - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &[_]gpu.BindGroup.Entry{ - gpu.BindGroup.Entry.textureView(0, app.draw_texture_view), - gpu.BindGroup.Entry.sampler(1, draw_sampler), - gpu.BindGroup.Entry.textureView(2, app.depth_texture_view), - gpu.BindGroup.Entry.sampler(3, depth_sampler), - gpu.BindGroup.Entry.textureView(4, app.normal_texture_view), - gpu.BindGroup.Entry.sampler(5, normal_sampler), - gpu.BindGroup.Entry.buffer(6, uniform_buffer, 0, @sizeOf(PostUniformBufferObject)), - }, - }), - ); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = vertex, - .primitive = .{ - .cull_mode = .back, - }, - }; - - app.post_pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.post_vertex_buffer = vertex_buffer; - app.post_uniform_buffer = uniform_buffer; - app.post_bind_group = bind_group; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); - bgl.release(); -} diff --git a/examples/pixel-post-process/normal_frag.wgsl b/examples/pixel-post-process/normal_frag.wgsl deleted file mode 100644 index 28f7407c..00000000 --- a/examples/pixel-post-process/normal_frag.wgsl +++ /dev/null @@ -1,6 +0,0 @@ -@fragment fn main( - @location(0) normal: vec3, - @location(1) uv: vec2, -) -> @location(0) vec4 { - return vec4(normal / 2 + 0.5, 1.0); -} diff --git a/examples/pixel-post-process/pixel_frag.wgsl b/examples/pixel-post-process/pixel_frag.wgsl deleted file mode 100644 index e2d23d8f..00000000 --- a/examples/pixel-post-process/pixel_frag.wgsl +++ /dev/null @@ -1,72 +0,0 @@ -@group(0) @binding(0) -var draw_texture: texture_2d; -@group(0) @binding(1) -var draw_texture_sampler: sampler; - -@group(0) @binding(2) -var depth_texture: texture_depth_2d; -@group(0) @binding(3) -var depth_texture_sampler: sampler; - -@group(0) @binding(4) -var normal_texture: texture_2d; -@group(0) @binding(5) -var normal_texture_sampler: sampler; - -struct View { - @location(0) width: u32, - @location(1) height: u32, - @location(2) pixel_size: u32, -} -@group(0) @binding(6) -var view: View; - -fn sample_depth(uv: vec2, x: f32, y: f32) -> f32 { - return textureSample( - depth_texture, - depth_texture_sampler, - uv + vec2(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height)) - ); -} - -fn sample_normal(uv: vec2, x: f32, y: f32) -> vec3 { - return textureSample( - normal_texture, - normal_texture_sampler, - uv + vec2(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height)) - ).xyz; -} - -fn normal_indicator(uv: vec2, x: f32, y: f32) -> f32 { - var depth_diff = sample_depth(uv, 0, 0) - sample_depth(uv, x, y); - if (depth_diff > 0) { - // only sample normals from closest pixel - return 0; - } - return distance(sample_normal(uv, 0, 0), sample_normal(uv, x, y)); -} - -@fragment fn main( - @builtin(position) position: vec4, - @location(0) uv: vec2 -) -> @location(0) vec4 { - var depth = sample_depth(uv, 0, 0); - var depth_diff: f32 = 0; - depth_diff += abs(depth - sample_depth(uv, -1, 0)); - depth_diff += abs(depth - sample_depth(uv, 1, 0)); - depth_diff += abs(depth - sample_depth(uv, 0, -1)); - depth_diff += abs(depth - sample_depth(uv, 0, 1)); - - var normal_diff: f32 = 0; - normal_diff += normal_indicator(uv, -1, 0); - normal_diff += normal_indicator(uv, 1, 0); - normal_diff += normal_indicator(uv, 0, -1); - normal_diff += normal_indicator(uv, 0, 1); - - var color = textureSample(draw_texture, draw_texture_sampler, uv); - if (depth_diff > 0.007) { // magic number from testing - return color * 0.7; - } - // add instead of multiply so really dark pixels get brighter - return color + (vec4(1) * step(0.1, normal_diff) * 0.7); -} diff --git a/examples/pixel-post-process/pixel_vert.wgsl b/examples/pixel-post-process/pixel_vert.wgsl deleted file mode 100644 index 734f629e..00000000 --- a/examples/pixel-post-process/pixel_vert.wgsl +++ /dev/null @@ -1,14 +0,0 @@ -struct VertexOut { - @builtin(position) position_clip: vec4, - @location(0) uv: vec2 -} - -@vertex fn main( - @location(0) position: vec3, - @location(1) uv: vec2 -) -> VertexOut { - var output : VertexOut; - output.position_clip = vec4(position.xy, 0.0, 1.0); - output.uv = uv; - return output; -} diff --git a/examples/pixel-post-process/quad_mesh.zig b/examples/pixel-post-process/quad_mesh.zig deleted file mode 100644 index 57db212f..00000000 --- a/examples/pixel-post-process/quad_mesh.zig +++ /dev/null @@ -1,13 +0,0 @@ -pub const Quad = extern struct { - pos: @Vector(3, f32), - uv: @Vector(2, f32), -}; - -pub const quad = [_]Quad{ - .{ .pos = .{ -1.0, 1.0, 0.0 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1.0, -1.0, 0.0 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1.0, 1.0, 0.0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1.0, 1.0, 0.0 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1.0, -1.0, 0.0 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1.0, -1.0, 0.0 }, .uv = .{ 1, 0 } }, -}; diff --git a/examples/pixel-post-process/vert.wgsl b/examples/pixel-post-process/vert.wgsl deleted file mode 100644 index 01c1e107..00000000 --- a/examples/pixel-post-process/vert.wgsl +++ /dev/null @@ -1,20 +0,0 @@ -@group(0) @binding(0) var ubo: mat4x4; - -struct VertexOut { - @builtin(position) position_clip: vec4, - @location(0) normal: vec3, - @location(1) uv: vec2, -} - -@vertex fn main( - @location(0) position: vec3, - @location(1) normal: vec3, - @location(2) uv: vec2 -) -> VertexOut { - var output: VertexOut; - output.position_clip = vec4(position, 1) * ubo; - output.normal = (vec4(normal, 0) * ubo).xyz; - output.uv = uv; - return output; -} - diff --git a/examples/rotating-cube/cube_mesh.zig b/examples/rotating-cube/cube_mesh.zig deleted file mode 100644 index f26c75ac..00000000 --- a/examples/rotating-cube/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(4, f32), - col: @Vector(4, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, -}; diff --git a/examples/rotating-cube/frag.wgsl b/examples/rotating-cube/frag.wgsl deleted file mode 100755 index 15ed50c9..00000000 --- a/examples/rotating-cube/frag.wgsl +++ /dev/null @@ -1,6 +0,0 @@ -@fragment fn main( - @location(0) fragUV: vec2, - @location(1) fragPosition: vec4 -) -> @location(0) vec4 { - return fragPosition; -} diff --git a/examples/rotating-cube/main.zig b/examples/rotating-cube/main.zig deleted file mode 100755 index 317d42ef..00000000 --- a/examples/rotating-cube/main.zig +++ /dev/null @@ -1,178 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; - -pub const App = @This(); - -const UniformBufferObject = struct { - mat: zm.Mat, -}; - -var timer: mach.Timer = undefined; - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, - -pub fn init(app: *App, core: *mach.Core) !void { - timer = try mach.Timer.start(); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{bgle}, - }), - ); - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }), - .primitive = .{ - .cull_mode = .back, - }, - }; - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject), - .mapped_at_creation = false, - }); - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - }, - }), - ); - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = core.device.getQueue(); - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group = bind_group; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); - bgl.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.vertex_buffer.release(); - app.uniform_buffer.release(); - app.bind_group.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - - { - const time = timer.read(); - const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0))); - const view = zm.lookAtRh( - zm.f32x4(0, 4, 2, 1), - zm.f32x4(0, 0, 0, 1), - zm.f32x4(0, 0, 1, 0), - ); - const proj = zm.perspectiveFovRh( - (std.math.pi / 4.0), - @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height), - 0.1, - 10, - ); - const mvp = zm.mul(zm.mul(model, view), proj); - const ubo = UniformBufferObject{ - .mat = zm.transpose(mvp), - }; - encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo}); - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - pass.setBindGroup(0, app.bind_group, &.{0}); - pass.draw(vertices.len, 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} diff --git a/examples/rotating-cube/vert.wgsl b/examples/rotating-cube/vert.wgsl deleted file mode 100755 index bb9a5ba0..00000000 --- a/examples/rotating-cube/vert.wgsl +++ /dev/null @@ -1,17 +0,0 @@ -@group(0) @binding(0) var ubo : mat4x4; -struct VertexOut { - @builtin(position) position_clip : vec4, - @location(0) fragUV : vec2, - @location(1) fragPosition: vec4, -} - -@vertex fn main( - @location(0) position : vec4, - @location(1) uv: vec2 -) -> VertexOut { - var output : VertexOut; - output.position_clip = position * ubo; - output.fragUV = uv; - output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); - return output; -} diff --git a/examples/sysaudio/main.zig b/examples/sysaudio/main.zig deleted file mode 100644 index 0551b31a..00000000 --- a/examples/sysaudio/main.zig +++ /dev/null @@ -1,212 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const sysaudio = mach.sysaudio; -const js = mach.sysjs; -const builtin = @import("builtin"); - -pub const App = @This(); - -audio: sysaudio, -device: *sysaudio.Device, -tone_engine: ToneEngine = .{}, - -pub fn init(app: *App, core: *mach.Core) !void { - const audio = try sysaudio.init(); - errdefer audio.deinit(); - - var device = try audio.requestDevice(core.allocator, .{ .mode = .output, .channels = 2 }); - errdefer device.deinit(core.allocator); - - device.setCallback(callback, app); - try device.start(); - - app.audio = audio; - app.device = device; -} - -fn callback(device: *sysaudio.Device, user_data: ?*anyopaque, buffer: []u8) void { - // TODO(sysaudio): should make user_data pointer type-safe - const app: *App = @ptrCast(*App, @alignCast(@alignOf(App), user_data)); - - // Where the magic happens: fill our audio buffer with PCM dat. - app.tone_engine.render(device.properties, buffer); -} - -pub fn deinit(app: *App, core: *mach.Core) void { - app.device.deinit(core.allocator); - app.audio.deinit(); -} - -pub fn update(app: *App, engine: *mach.Core) !void { - while (engine.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - try app.device.start(); - app.tone_engine.play(app.device.properties, ToneEngine.keyToFrequency(ev.key)); - }, - else => {}, - } - } - - if (builtin.cpu.arch != .wasm32) { - const back_buffer_view = engine.swap_chain.?.getCurrentTextureView(); - - engine.swap_chain.?.present(); - back_buffer_view.release(); - } -} - -// A simple tone engine. -// -// It renders 2048 tones simultaneously, each with their own frequency and duration. -// -// `keyToFrequency` can be used to convert a keyboard key to a frequency, so that the -// keys asdfghj on your QWERTY keyboard will map to the notes C/D/E/F/G/A/B[4], the -// keys above qwertyu will map to C5 and the keys below zxcvbnm will map to C3. -// -// The duration is hard-coded to 1.5s. To prevent clicking, tones are faded in linearly over -// the first 1/64th duration of the tone. To provide a cool sustained effect, tones are faded -// out using 1-log10(x*10) (google it to see how it looks, it's strong for most of the duration of -// the note then fades out slowly.) -pub const ToneEngine = struct { - playing: [2048]Tone = std.mem.zeroes([2048]Tone), - - const Tone = struct { - frequency: f32, - sample_counter: usize, - duration: usize, - }; - - pub fn render(engine: *ToneEngine, properties: sysaudio.Device.Properties, buffer: []u8) void { - switch (properties.format) { - .U8 => renderWithType(u8, engine, properties, buffer), - .S16 => { - const buf = @ptrCast([*]i16, @alignCast(@alignOf(i16), buffer.ptr))[0 .. buffer.len / @sizeOf(i16)]; - renderWithType(i16, engine, properties, buf); - }, - .S24 => { - const buf = @ptrCast([*]i24, @alignCast(@alignOf(i24), buffer.ptr))[0 .. buffer.len / @sizeOf(i24)]; - renderWithType(i24, engine, properties, buf); - }, - .S32 => { - const buf = @ptrCast([*]i32, @alignCast(@alignOf(i32), buffer.ptr))[0 .. buffer.len / @sizeOf(i32)]; - renderWithType(i32, engine, properties, buf); - }, - .F32 => { - const buf = @ptrCast([*]f32, @alignCast(@alignOf(f32), buffer.ptr))[0 .. buffer.len / @sizeOf(f32)]; - renderWithType(f32, engine, properties, buf); - }, - } - } - - pub fn renderWithType(comptime T: type, engine: *ToneEngine, properties: sysaudio.Device.Properties, buffer: []T) void { - const sample_rate = @intToFloat(f32, properties.sample_rate); - const frames = buffer.len / properties.channels; - - var frame: usize = 0; - while (frame < frames) : (frame += 1) { - // Render the sample for this frame (e.g. for both left and right audio channels.) - var sample: f32 = 0; - for (engine.playing) |*tone| { - if (tone.sample_counter >= tone.duration) { - continue; - } - tone.sample_counter += 1; - const sample_counter = @intToFloat(f32, tone.sample_counter); - const duration = @intToFloat(f32, tone.duration); - - // The sine wave that plays the frequency. - const gain = 0.1; - const sine_wave = std.math.sin(tone.frequency * 2.0 * std.math.pi * sample_counter / sample_rate) * gain; - - // A number ranging from 0.0 to 1.0 in the first 1/64th of the duration of the tone. - const fade_in = std.math.min(sample_counter / (duration / 64.0), 1.0); - - // A number ranging from 1.0 to 0.0 over half the duration of the tone. - const progression = sample_counter / duration; // 0.0 (tone start) to 1.0 (tone end) - const fade_out = 1.0 - std.math.clamp(std.math.log10(progression * 10.0), 0.0, 1.0); - - // Mix this tone into the sample we'll actually play on e.g. the speakers, reducing - // sine wave intensity if we're fading in or out over the entire duration of the - // tone. - sample += sine_wave * fade_in * fade_out; - } - - const sample_t: T = sample: { - switch (T) { - f32 => break :sample sample, - u8 => break :sample @floatToInt(u8, (sample + 1.0) * 255), - else => break :sample @floatToInt(T, sample * std.math.maxInt(T)), - } - }; - - // Emit the sample on all channels. - var channel: usize = 0; - while (channel < properties.channels) : (channel += 1) { - var channel_buffer = buffer[channel * frames .. (channel + 1) * frames]; - channel_buffer[frame] = sample_t; - } - } - } - - pub fn play(engine: *ToneEngine, properties: sysaudio.Device.Properties, frequency: f32) void { - const sample_rate = @intToFloat(f32, properties.sample_rate); - - for (engine.playing) |*tone| { - if (tone.sample_counter >= tone.duration) { - tone.* = Tone{ - .frequency = frequency, - .sample_counter = 0, - .duration = @floatToInt(usize, 1.5 * sample_rate), // play the tone for 1.5s - }; - return; - } - } - } - - pub fn keyToFrequency(key: mach.Key) f32 { - // The frequencies here just come from a piano frequencies chart. You can google for them. - return switch (key) { - // First row of piano keys, the highest. - .q => 523.25, // C5 - .w => 587.33, // D5 - .e => 659.26, // E5 - .r => 698.46, // F5 - .t => 783.99, // G5 - .y => 880.0, // A5 - .u => 987.77, // B5 - .i => 1046.5, // C6 - .o => 1174.7, // D6 - .p => 1318.5, // E6 - .left_bracket => 1396.9, // F6 - .right_bracket => 1568.0, // G6 - - // Second row of piano keys, the middle. - .a => 261.63, // C4 - .s => 293.67, // D4 - .d => 329.63, // E4 - .f => 349.23, // F4 - .g => 392.0, // G4 - .h => 440.0, // A4 - .j => 493.88, // B4 - .k => 523.25, // C5 - .l => 587.33, // D5 - .semicolon => 659.26, // E5 - .apostrophe => 698.46, // F5 - - // Third row of piano keys, the lowest. - .z => 130.81, // C3 - .x => 146.83, // D3 - .c => 164.81, // E3 - .v => 174.61, // F3 - .b => 196.00, // G3 - .n => 220.0, // A3 - .m => 246.94, // B3 - .comma => 261.63, // C4 - .period => 293.67, // D4 - .slash => 329.63, // E5 - else => 0.0, - }; - } -}; diff --git a/examples/textured-cube/assets b/examples/textured-cube/assets deleted file mode 160000 index b5a84047..00000000 --- a/examples/textured-cube/assets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b5a8404715e6cfc57e66c4a7cc0625e64b3b3c56 diff --git a/examples/textured-cube/cube_mesh.zig b/examples/textured-cube/cube_mesh.zig deleted file mode 100644 index ae5b2912..00000000 --- a/examples/textured-cube/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(4, f32), - col: @Vector(4, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, -}; diff --git a/examples/textured-cube/frag.wgsl b/examples/textured-cube/frag.wgsl deleted file mode 100644 index eff6d2c3..00000000 --- a/examples/textured-cube/frag.wgsl +++ /dev/null @@ -1,8 +0,0 @@ -@group(0) @binding(1) var mySampler: sampler; -@group(0) @binding(2) var myTexture: texture_2d; - -@fragment -fn main(@location(0) fragUV: vec2, - @location(1) fragPosition: vec4) -> @location(0) vec4 { - return textureSample(myTexture, mySampler, fragUV); -} diff --git a/examples/textured-cube/main.zig b/examples/textured-cube/main.zig deleted file mode 100644 index 714a0c9c..00000000 --- a/examples/textured-cube/main.zig +++ /dev/null @@ -1,268 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); -const zigimg = @import("zigimg"); -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; - -const UniformBufferObject = struct { - mat: zm.Mat, -}; - -var timer: mach.Timer = undefined; - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, -depth_texture: ?*gpu.Texture, -depth_texture_view: *gpu.TextureView, - -pub const App = @This(); - -pub fn init(app: *App, core: *mach.Core) !void { - timer = try mach.Timer.start(); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const blend = gpu.BlendState{ - .color = .{ - .operation = .add, - .src_factor = .src_alpha, - .dst_factor = .one_minus_src_alpha, - }, - .alpha = .{ - .operation = .add, - .src_factor = .one, - .dst_factor = .zero, - }, - }; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - // Enable depth testing so that the fragment closest to the camera - // is rendered in front. - .depth_stencil = &.{ - .format = .depth24_plus, - .depth_write_enabled = true, - .depth_compare = .less, - }, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }), - .primitive = .{ - // Backface culling since the cube is solid piece of geometry. - // Faces pointing away from the camera will be occluded by faces - // pointing toward the camera. - .cull_mode = .back, - }, - }; - const pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - // Create a sampler with linear filtering for smooth interpolation. - const sampler = core.device.createSampler(&.{ - .mag_filter = .linear, - .min_filter = .linear, - }); - const queue = core.device.getQueue(); - var img = try zigimg.Image.fromMemory(core.allocator, @embedFile("./assets/gotta-go-fast.png")); - defer img.deinit(); - const img_size = gpu.Extent3D{ .width = @intCast(u32, img.width), .height = @intCast(u32, img.height) }; - const cube_texture = core.device.createTexture(&.{ - .size = img_size, - .format = .rgba8_unorm, - .usage = .{ - .texture_binding = true, - .copy_dst = true, - .render_attachment = true, - }, - }); - const data_layout = gpu.Texture.DataLayout{ - .bytes_per_row = @intCast(u32, img.width * 4), - .rows_per_image = @intCast(u32, img.height), - }; - switch (img.pixels) { - .rgba32 => |pixels| queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, pixels), - .rgb24 => |pixels| { - const data = try rgb24ToRgba32(core.allocator, pixels); - defer data.deinit(core.allocator); - queue.writeTexture(&.{ .texture = cube_texture }, &data_layout, &img_size, data.rgba32); - }, - else => @panic("unsupported image color format"), - } - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject), - .mapped_at_creation = false, - }); - - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = pipeline.getBindGroupLayout(0), - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - gpu.BindGroup.Entry.sampler(1, sampler), - gpu.BindGroup.Entry.textureView(2, cube_texture.createView(&gpu.TextureView.Descriptor{})), - }, - }), - ); - - app.pipeline = pipeline; - app.queue = queue; - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group = bind_group; - app.depth_texture = null; - app.depth_texture_view = undefined; - - vs_module.release(); - fs_module.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.vertex_buffer.release(); - app.uniform_buffer.release(); - app.bind_group.release(); - app.depth_texture.?.release(); - app.depth_texture_view.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = .{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 0.0 }, - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - .depth_stencil_attachment = &.{ - .view = app.depth_texture_view, - .depth_clear_value = 1.0, - .depth_load_op = .clear, - .depth_store_op = .store, - }, - }); - - { - const time = timer.read(); - const model = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0))); - const view = zm.lookAtRh( - zm.f32x4(0, 4, 2, 1), - zm.f32x4(0, 0, 0, 1), - zm.f32x4(0, 0, 1, 0), - ); - const proj = zm.perspectiveFovRh( - (std.math.pi / 4.0), - @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height), - 0.1, - 10, - ); - const mvp = zm.mul(zm.mul(model, view), proj); - const ubo = UniformBufferObject{ - .mat = zm.transpose(mvp), - }; - encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo}); - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - pass.setBindGroup(0, app.bind_group, &.{}); - pass.draw(vertices.len, 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} - -pub fn resize(app: *App, core: *mach.Core, width: u32, height: u32) !void { - // If window is resized, recreate depth buffer otherwise we cannot use it. - if (app.depth_texture != null) { - app.depth_texture.?.release(); - app.depth_texture_view.release(); - } - app.depth_texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .size = gpu.Extent3D{ - .width = width, - .height = height, - }, - .format = .depth24_plus, - .usage = .{ - .render_attachment = true, - .texture_binding = true, - }, - }); - - app.depth_texture_view = app.depth_texture.?.createView(&gpu.TextureView.Descriptor{ - .format = .depth24_plus, - .dimension = .dimension_2d, - .array_layer_count = 1, - .mip_level_count = 1, - }); -} - -fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []zigimg.color.Rgb24) !zigimg.color.PixelStorage { - const out = try zigimg.color.PixelStorage.init(allocator, .rgba32, in.len); - var i: usize = 0; - while (i < in.len) : (i += 1) { - out.rgba32[i] = zigimg.color.Rgba32{ .r = in[i].r, .g = in[i].g, .b = in[i].b, .a = 255 }; - } - return out; -} diff --git a/examples/textured-cube/vert.wgsl b/examples/textured-cube/vert.wgsl deleted file mode 100644 index dac516ce..00000000 --- a/examples/textured-cube/vert.wgsl +++ /dev/null @@ -1,20 +0,0 @@ -struct Uniforms { - modelViewProjectionMatrix : mat4x4, -}; -@binding(0) @group(0) var uniforms : Uniforms; - -struct VertexOutput { - @builtin(position) Position : vec4, - @location(0) fragUV : vec2, - @location(1) fragPosition: vec4, -}; - -@vertex -fn main(@location(0) position : vec4, - @location(1) uv : vec2) -> VertexOutput { - var output : VertexOutput; - output.Position = position * uniforms.modelViewProjectionMatrix; - output.fragUV = uv; - output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); - return output; -} diff --git a/examples/triangle-msaa/frag.wgsl b/examples/triangle-msaa/frag.wgsl deleted file mode 100644 index 285632d5..00000000 --- a/examples/triangle-msaa/frag.wgsl +++ /dev/null @@ -1,3 +0,0 @@ -@fragment fn main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} diff --git a/examples/triangle-msaa/main.zig b/examples/triangle-msaa/main.zig deleted file mode 100644 index de728b41..00000000 --- a/examples/triangle-msaa/main.zig +++ /dev/null @@ -1,114 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); - -pub const App = @This(); - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -texture: *gpu.Texture, -texture_view: *gpu.TextureView, -window_title_timer: mach.Timer, - -const sample_count = 4; - -pub fn init(app: *App, core: *mach.Core) !void { - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - // Fragment state - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .vertex = gpu.VertexState{ - .module = vs_module, - .entry_point = "main", - }, - .multisample = gpu.MultisampleState{ - .count = sample_count, - }, - }; - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = core.device.getQueue(); - - app.texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .size = gpu.Extent3D{ - .width = core.current_desc.width, - .height = core.current_desc.height, - }, - .sample_count = sample_count, - .format = core.swap_chain_format, - .usage = .{ .render_attachment = true }, - }); - app.texture_view = app.texture.createView(null); - - vs_module.release(); - fs_module.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.texture.release(); - app.texture_view.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = app.texture_view, - .resolve_target = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .discard, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.draw(3, 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); - - if (app.window_title_timer.read() >= 1.0) { - const window_title = try std.fmt.allocPrintZ(core.allocator, "FPS: {d}", .{@floatToInt(u32, 1 / core.delta_time)}); - defer core.allocator.free(window_title); - try core.internal.window.setTitle(window_title); - app.window_title_timer.reset(); - } -} - -pub fn resize(app: *App, core: *mach.Core, width: u32, height: u32) !void { - app.texture.release(); - app.texture = core.device.createTexture(&gpu.Texture.Descriptor{ - .size = gpu.Extent3D{ - .width = width, - .height = height, - }, - .sample_count = sample_count, - .format = core.swap_chain_format, - .usage = .{ .render_attachment = true }, - }); - app.texture_view.release(); - app.texture_view = app.texture.createView(null); -} diff --git a/examples/triangle-msaa/vert.wgsl b/examples/triangle-msaa/vert.wgsl deleted file mode 100644 index 183333b1..00000000 --- a/examples/triangle-msaa/vert.wgsl +++ /dev/null @@ -1,10 +0,0 @@ -@vertex fn main( - @builtin(vertex_index) VertexIndex : u32 -) -> @builtin(position) vec4 { - var pos = array, 3>( - vec2( 0.0, 0.5), - vec2(-0.5, -0.5), - vec2( 0.5, -0.5) - ); - return vec4(pos[VertexIndex], 0.0, 1.0); -} diff --git a/examples/triangle/frag.wgsl b/examples/triangle/frag.wgsl deleted file mode 100644 index 285632d5..00000000 --- a/examples/triangle/frag.wgsl +++ /dev/null @@ -1,3 +0,0 @@ -@fragment fn main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} diff --git a/examples/triangle/main.zig b/examples/triangle/main.zig deleted file mode 100644 index 0a1ac396..00000000 --- a/examples/triangle/main.zig +++ /dev/null @@ -1,69 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); - -pub const App = @This(); - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, - -pub fn init(app: *App, core: *mach.Core) !void { - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - // Fragment state - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .vertex = gpu.VertexState{ - .module = vs_module, - .entry_point = "main", - }, - }; - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = core.device.getQueue(); - - vs_module.release(); - fs_module.release(); -} - -pub fn deinit(_: *App, _: *mach.Core) void {} - -pub fn update(app: *App, core: *mach.Core) !void { - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.draw(3, 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} diff --git a/examples/triangle/vert.wgsl b/examples/triangle/vert.wgsl deleted file mode 100644 index 183333b1..00000000 --- a/examples/triangle/vert.wgsl +++ /dev/null @@ -1,10 +0,0 @@ -@vertex fn main( - @builtin(vertex_index) VertexIndex : u32 -) -> @builtin(position) vec4 { - var pos = array, 3>( - vec2( 0.0, 0.5), - vec2(-0.5, -0.5), - vec2( 0.5, -0.5) - ); - return vec4(pos[VertexIndex], 0.0, 1.0); -} diff --git a/examples/two-cubes/cube_mesh.zig b/examples/two-cubes/cube_mesh.zig deleted file mode 100644 index f26c75ac..00000000 --- a/examples/two-cubes/cube_mesh.zig +++ /dev/null @@ -1,49 +0,0 @@ -pub const Vertex = extern struct { - pos: @Vector(4, f32), - col: @Vector(4, f32), - uv: @Vector(2, f32), -}; - -pub const vertices = [_]Vertex{ - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, 1, 1 }, .col = .{ 0, 1, 1, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ -1, -1, 1, 1 }, .col = .{ 0, 0, 1, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, -1, 1, 1 }, .col = .{ 1, 0, 1, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, 1, 1, 1 }, .col = .{ 1, 1, 1, 1 }, .uv = .{ 1, 1 } }, - - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, -1, -1, 1 }, .col = .{ 0, 0, 0, 1 }, .uv = .{ 0, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, - .{ .pos = .{ 1, 1, -1, 1 }, .col = .{ 1, 1, 0, 1 }, .uv = .{ 1, 0 } }, - .{ .pos = .{ 1, -1, -1, 1 }, .col = .{ 1, 0, 0, 1 }, .uv = .{ 1, 1 } }, - .{ .pos = .{ -1, 1, -1, 1 }, .col = .{ 0, 1, 0, 1 }, .uv = .{ 0, 0 } }, -}; diff --git a/examples/two-cubes/frag.wgsl b/examples/two-cubes/frag.wgsl deleted file mode 100755 index 15ed50c9..00000000 --- a/examples/two-cubes/frag.wgsl +++ /dev/null @@ -1,6 +0,0 @@ -@fragment fn main( - @location(0) fragUV: vec2, - @location(1) fragPosition: vec4 -) -> @location(0) vec4 { - return fragPosition; -} diff --git a/examples/two-cubes/main.zig b/examples/two-cubes/main.zig deleted file mode 100755 index ccc06428..00000000 --- a/examples/two-cubes/main.zig +++ /dev/null @@ -1,210 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const gpu = @import("gpu"); -const glfw = @import("glfw"); -const zm = @import("zmath"); -const Vertex = @import("cube_mesh.zig").Vertex; -const vertices = @import("cube_mesh.zig").vertices; - -const UniformBufferObject = struct { - mat: zm.Mat, -}; - -var timer: mach.Timer = undefined; - -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -vertex_buffer: *gpu.Buffer, -uniform_buffer: *gpu.Buffer, -bind_group1: *gpu.BindGroup, -bind_group2: *gpu.BindGroup, - -pub const App = @This(); - -pub fn init(app: *App, core: *mach.Core) !void { - timer = try mach.Timer.start(); - - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - - const vertex_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .offset = @offsetOf(Vertex, "pos"), .shader_location = 0 }, - .{ .format = .float32x2, .offset = @offsetOf(Vertex, "uv"), .shader_location = 1 }, - }; - const vertex_buffer_layout = gpu.VertexBufferLayout.init(.{ - .array_stride = @sizeOf(Vertex), - .step_mode = .vertex, - .attributes = &vertex_attributes, - }); - - const fs_module = core.device.createShaderModuleWGSL("frag.wgsl", @embedFile("frag.wgsl")); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.swap_chain_format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); - const bgl = core.device.createBindGroupLayout( - &gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{bgle}, - }), - ); - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - .buffers = &.{vertex_buffer_layout}, - }), - .primitive = .{ - .cull_mode = .back, - }, - }; - - const queue = core.device.getQueue(); - - const vertex_buffer = core.device.createBuffer(&.{ - .usage = .{ .vertex = true }, - .size = @sizeOf(Vertex) * vertices.len, - .mapped_at_creation = true, - }); - var vertex_mapped = vertex_buffer.getMappedRange(Vertex, 0, vertices.len); - std.mem.copy(Vertex, vertex_mapped.?, vertices[0..]); - vertex_buffer.unmap(); - - // uniformBindGroup offset must be 256-byte aligned - const uniform_offset = 256; - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .uniform = true, .copy_dst = true }, - .size = @sizeOf(UniformBufferObject) + uniform_offset, - .mapped_at_creation = false, - }); - - const bind_group1 = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - }, - }), - ); - - const bind_group2 = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, uniform_offset, @sizeOf(UniformBufferObject)), - }, - }), - ); - - app.pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - app.queue = queue; - app.vertex_buffer = vertex_buffer; - app.uniform_buffer = uniform_buffer; - app.bind_group1 = bind_group1; - app.bind_group2 = bind_group2; - - vs_module.release(); - fs_module.release(); - pipeline_layout.release(); - bgl.release(); -} - -pub fn deinit(app: *App, _: *mach.Core) void { - app.vertex_buffer.release(); - app.uniform_buffer.release(); - app.bind_group1.release(); - app.bind_group2.release(); -} - -pub fn update(app: *App, core: *mach.Core) !void { - while (core.pollEvent()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) - core.close(); - }, - else => {}, - } - } - - const back_buffer_view = core.swap_chain.?.getCurrentTextureView(); - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - - { - const time = timer.read(); - const rotation1 = zm.mul(zm.rotationX(time * (std.math.pi / 2.0)), zm.rotationZ(time * (std.math.pi / 2.0))); - const rotation2 = zm.mul(zm.rotationZ(time * (std.math.pi / 2.0)), zm.rotationX(time * (std.math.pi / 2.0))); - const model1 = zm.mul(rotation1, zm.translation(-2, 0, 0)); - const model2 = zm.mul(rotation2, zm.translation(2, 0, 0)); - const view = zm.lookAtRh( - zm.f32x4(0, -4, 2, 1), - zm.f32x4(0, 0, 0, 1), - zm.f32x4(0, 0, 1, 0), - ); - const proj = zm.perspectiveFovRh( - (2.0 * std.math.pi / 5.0), - @intToFloat(f32, core.current_desc.width) / @intToFloat(f32, core.current_desc.height), - 1, - 100, - ); - const mvp1 = zm.mul(zm.mul(model1, view), proj); - const mvp2 = zm.mul(zm.mul(model2, view), proj); - const ubo1 = UniformBufferObject{ - .mat = zm.transpose(mvp1), - }; - const ubo2 = UniformBufferObject{ - .mat = zm.transpose(mvp2), - }; - - encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo1}); - - // bind_group2 offset - encoder.writeBuffer(app.uniform_buffer, 256, &[_]UniformBufferObject{ubo2}); - } - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(app.pipeline); - pass.setVertexBuffer(0, app.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); - - pass.setBindGroup(0, app.bind_group1, &.{0}); - pass.draw(vertices.len, 1, 0, 0); - pass.setBindGroup(0, app.bind_group2, &.{0}); - pass.draw(vertices.len, 1, 0, 0); - - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - app.queue.submit(&.{command}); - command.release(); - core.swap_chain.?.present(); - back_buffer_view.release(); -} diff --git a/examples/two-cubes/vert.wgsl b/examples/two-cubes/vert.wgsl deleted file mode 100755 index bb9a5ba0..00000000 --- a/examples/two-cubes/vert.wgsl +++ /dev/null @@ -1,17 +0,0 @@ -@group(0) @binding(0) var ubo : mat4x4; -struct VertexOut { - @builtin(position) position_clip : vec4, - @location(0) fragUV : vec2, - @location(1) fragPosition: vec4, -} - -@vertex fn main( - @location(0) position : vec4, - @location(1) uv: vec2 -) -> VertexOut { - var output : VertexOut; - output.position_clip = position * ubo; - output.fragUV = uv; - output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); - return output; -}