{mach,examples}: move examples to github.com/hexops/mach-examples
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
1cbef1f7e1
commit
189997c279
77 changed files with 2 additions and 11016 deletions
20
.gitmodules
vendored
20
.gitmodules
vendored
|
|
@ -9,13 +9,6 @@
|
||||||
shallow = true
|
shallow = true
|
||||||
branch = "mach"
|
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"]
|
[submodule "freetype/upstream"]
|
||||||
path = libs/freetype/upstream
|
path = libs/freetype/upstream
|
||||||
url = https://github.com/hexops/freetype
|
url = https://github.com/hexops/freetype
|
||||||
|
|
@ -28,15 +21,4 @@
|
||||||
[submodule "basisu/upstream"]
|
[submodule "basisu/upstream"]
|
||||||
path = libs/basisu/upstream
|
path = libs/basisu/upstream
|
||||||
url = https://github.com/hexops/basisu
|
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
|
|
||||||
|
|
|
||||||
118
build.zig
118
build.zig
|
|
@ -110,84 +110,10 @@ pub fn build(b: *Builder) !void {
|
||||||
shared_lib.install();
|
shared_lib.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
try ensureExamplesDependencySubmodules(b.allocator);
|
const compile_all = b.step("compile-all", "Compile Mach");
|
||||||
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");
|
|
||||||
compile_all.dependOn(b.getInstallStep());
|
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 {
|
fn testStep(b: *Builder, mode: std.builtin.Mode, target: CrossTarget) *std.build.RunStep {
|
||||||
const main_tests = b.addTestExe("mach-tests", "src/main.zig");
|
const main_tests = b.addTestExe("mach-tests", "src/main.zig");
|
||||||
main_tests.setBuildMode(mode);
|
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 {
|
fn sdkPath(comptime suffix: []const u8) []const u8 {
|
||||||
if (suffix[0] != '/') @compileError("suffix must be an absolute path");
|
if (suffix[0] != '/') @compileError("suffix must be an absolute path");
|
||||||
return comptime blk: {
|
return comptime blk: {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
struct CameraUniform {
|
|
||||||
pos: vec4<f32>,
|
|
||||||
view_proj: mat4x4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InstanceInput {
|
|
||||||
@location(3) model_matrix_0: vec4<f32>,
|
|
||||||
@location(4) model_matrix_1: vec4<f32>,
|
|
||||||
@location(5) model_matrix_2: vec4<f32>,
|
|
||||||
@location(6) model_matrix_3: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexInput {
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@location(1) normal: vec3<f32>,
|
|
||||||
@location(2) tex_coords: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
@location(0) tex_coords: vec2<f32>,
|
|
||||||
@location(1) normal: vec3<f32>,
|
|
||||||
@location(2) position: vec3<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Light {
|
|
||||||
position: vec4<f32>,
|
|
||||||
color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> camera: CameraUniform;
|
|
||||||
@group(1) @binding(0) var t_diffuse: texture_2d<f32>;
|
|
||||||
@group(1) @binding(1) var s_diffuse: sampler;
|
|
||||||
@group(2) @binding(0) var<uniform> light: Light;
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput {
|
|
||||||
let model_matrix = mat4x4<f32>(
|
|
||||||
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<f32>(model.position, 1.0);
|
|
||||||
out.position = world_pos.xyz;
|
|
||||||
out.normal = (model_matrix * vec4<f32>(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<f32> {
|
|
||||||
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<f32>(result, object_color.a);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
struct CameraUniform {
|
|
||||||
view_pos: vec4<f32>,
|
|
||||||
view_proj: mat4x4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexInput {
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@location(1) normal: vec3<f32>,
|
|
||||||
@location(2) tex_coords: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Light {
|
|
||||||
position: vec4<f32>,
|
|
||||||
color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> camera: CameraUniform;
|
|
||||||
@group(1) @binding(0) var<uniform> light: Light;
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
|
||||||
var out: VertexOutput;
|
|
||||||
let world_pos = vec4<f32>(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<f32> {
|
|
||||||
return vec4<f32>(1.0, 1.0, 1.0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
@vertex
|
|
||||||
fn vert_main(@location(0) a_particlePos : vec2<f32>,
|
|
||||||
@location(1) a_particleVel : vec2<f32>,
|
|
||||||
@location(2) a_pos : vec2<f32>) -> @builtin(position) vec4<f32> {
|
|
||||||
let angle = -atan2(a_particleVel.x, a_particleVel.y);
|
|
||||||
let pos = vec2<f32>(
|
|
||||||
(a_pos.x * cos(angle)) - (a_pos.y * sin(angle)),
|
|
||||||
(a_pos.x * sin(angle)) + (a_pos.y * cos(angle)));
|
|
||||||
return vec4<f32>(pos + a_particlePos, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn frag_main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
struct Particle {
|
|
||||||
pos : vec2<f32>,
|
|
||||||
vel : vec2<f32>,
|
|
||||||
};
|
|
||||||
struct SimParams {
|
|
||||||
deltaT : f32,
|
|
||||||
rule1Distance : f32,
|
|
||||||
rule2Distance : f32,
|
|
||||||
rule3Distance : f32,
|
|
||||||
rule1Scale : f32,
|
|
||||||
rule2Scale : f32,
|
|
||||||
rule3Scale : f32,
|
|
||||||
};
|
|
||||||
struct Particles {
|
|
||||||
particles : array<Particle>,
|
|
||||||
};
|
|
||||||
@binding(0) @group(0) var<uniform> params : SimParams;
|
|
||||||
@binding(1) @group(0) var<storage, read> particlesA : Particles;
|
|
||||||
@binding(2) @group(0) var<storage, read_write> 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<u32>) {
|
|
||||||
var index : u32 = GlobalInvocationID.x;
|
|
||||||
|
|
||||||
var vPos = particlesA.particles[index].pos;
|
|
||||||
var vVel = particlesA.particles[index].vel;
|
|
||||||
var cMass = vec2<f32>(0.0, 0.0);
|
|
||||||
var cVel = vec2<f32>(0.0, 0.0);
|
|
||||||
var colVel = vec2<f32>(0.0, 0.0);
|
|
||||||
var cMassCount : u32 = 0u;
|
|
||||||
var cVelCount : u32 = 0u;
|
|
||||||
var pos : vec2<f32>;
|
|
||||||
var vel : vec2<f32>;
|
|
||||||
|
|
||||||
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<f32>(temp, temp)) - vPos;
|
|
||||||
}
|
|
||||||
if (cVelCount > 0u) {
|
|
||||||
var temp = f32(cVelCount);
|
|
||||||
cVel = cVel / vec2<f32>(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;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit a106279bd4a5c4a7b6860cac6a0a0c0a6c727401
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
@group(0) @binding(1) var mySampler: sampler;
|
|
||||||
@group(0) @binding(2) var myTexture: texture_cube<f32>;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn main(
|
|
||||||
@location(0) fragUV: vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
var cubemapVec = fragPosition.xyz - vec3<f32>(0.5, 0.5, 0.5);
|
|
||||||
return textureSample(myTexture, mySampler, cubemapVec);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
struct Uniforms {
|
|
||||||
modelViewProjectionMatrix : mat4x4<f32>,
|
|
||||||
}
|
|
||||||
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) Position : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn main(
|
|
||||||
@location(0) position : vec4<f32>,
|
|
||||||
@location(1) uv : vec2<f32>
|
|
||||||
) -> VertexOutput {
|
|
||||||
var output : VertexOutput;
|
|
||||||
output.Position = uniforms.modelViewProjectionMatrix * position;
|
|
||||||
output.fragUV = uv;
|
|
||||||
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -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?
|
|
||||||
}
|
|
||||||
|
|
@ -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!", .{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
@binding(1) @group(0) var mySampler: sampler;
|
|
||||||
@binding(2) @group(0) var myTexture: texture_2d<f32>;
|
|
||||||
|
|
||||||
@fragment fn main(
|
|
||||||
@location(0) fragUV: vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
let texColor = textureSample(myTexture, mySampler, fragUV * 0.8 + vec2<f32>(0.1, 0.1));
|
|
||||||
let f = f32(length(texColor.rgb - vec3<f32>(0.5, 0.5, 0.5)) < 0.01);
|
|
||||||
return (1.0 - f) * texColor + f * fragPosition;
|
|
||||||
// return vec4<f32>(texColor.rgb,1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
struct Uniforms {
|
|
||||||
matrix : mat4x4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@binding(0) @group(0) var<uniform> ubo : Uniforms;
|
|
||||||
|
|
||||||
struct VertexOut {
|
|
||||||
@builtin(position) Position : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex fn main(
|
|
||||||
@location(0) position : vec4<f32>,
|
|
||||||
@location(1) uv: vec2<f32>
|
|
||||||
) -> VertexOut {
|
|
||||||
var output : VertexOut;
|
|
||||||
output.Position = position * ubo.matrix;
|
|
||||||
output.fragUV = uv;
|
|
||||||
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
TODO: add license, the tesselator implementation comes from https://github.com/fubark/cosmic/blob/master/graphics/src/tessellator.zig
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit b5a8404715e6cfc57e66c4a7cc0625e64b3b3c56
|
|
||||||
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
struct FragUniform {
|
|
||||||
type_: u32,
|
|
||||||
padding: vec3<f32>,
|
|
||||||
blend_color: vec4<f32>,
|
|
||||||
}
|
|
||||||
@binding(1) @group(0) var<storage> ubos: array<FragUniform>;
|
|
||||||
@binding(2) @group(0) var mySampler: sampler;
|
|
||||||
@binding(3) @group(0) var myTexture: texture_2d<f32>;
|
|
||||||
|
|
||||||
@fragment fn main(
|
|
||||||
@location(0) uv: vec2<f32>,
|
|
||||||
@interpolate(linear) @location(1) bary: vec2<f32>,
|
|
||||||
@interpolate(flat) @location(2) triangle_index: u32,
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
// Example 1: Visualize barycentric coordinates:
|
|
||||||
// return vec4<f32>(bary.x, bary.y, 0.0, 1.0);
|
|
||||||
// return vec4<f32>(0.0, bary.x, 0.0, 1.0); // [1.0 (bottom-left vertex), 0.0 (bottom-right vertex)]
|
|
||||||
// return vec4<f32>(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<f32>(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<f32>(1.0, 0.0, 0.0, 1.0); }
|
|
||||||
// if (dist > 0.2 && dist <= 0.3) { return vec4<f32>(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<f32>(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<f32>(border_color.rgb, width);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return color * f32(dist >= 0.0 || ubos[triangle_index].type_ == 2u);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
struct VertexUniform {
|
|
||||||
matrix: mat4x4<f32>,
|
|
||||||
}
|
|
||||||
@binding(0) @group(0) var<uniform> ubo: VertexUniform;
|
|
||||||
|
|
||||||
struct VertexOut {
|
|
||||||
@builtin(position) position_clip: vec4<f32>,
|
|
||||||
@location(0) frag_uv: vec2<f32>,
|
|
||||||
@interpolate(linear) @location(1) frag_bary: vec2<f32>,
|
|
||||||
@interpolate(flat) @location(2) triangle_index: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex fn main(
|
|
||||||
@builtin(vertex_index) vertex_index: u32,
|
|
||||||
@location(0) position: vec4<f32>,
|
|
||||||
@location(1) uv: vec2<f32>,
|
|
||||||
) -> 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<f32>(0.0, 0.0);
|
|
||||||
// } else if ((vertex_index+1u) % 3u == 1u) {
|
|
||||||
// output.frag_bary = vec2<f32>(0.5, 0.0);
|
|
||||||
// } else {
|
|
||||||
// output.frag_bary = vec2<f32>(1.0, 1.0);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
output.frag_bary = vec2<f32>(
|
|
||||||
f32((vertex_index+1u) % 3u) * 0.5,
|
|
||||||
1.0 - f32((((vertex_index + 3u) % 3u) + 1u) % 2u),
|
|
||||||
);
|
|
||||||
output.triangle_index = vertex_index / 3u;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit b5a8404715e6cfc57e66c4a7cc0625e64b3b3c56
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
struct Params {
|
|
||||||
filterDim : u32,
|
|
||||||
blockDim : u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(0) @binding(0) var samp : sampler;
|
|
||||||
@group(0) @binding(1) var<uniform> params : Params;
|
|
||||||
@group(1) @binding(1) var inputTex : texture_2d<f32>;
|
|
||||||
@group(1) @binding(2) var outputTex : texture_storage_2d<rgba8unorm, write>;
|
|
||||||
|
|
||||||
struct Flip {
|
|
||||||
value : u32,
|
|
||||||
}
|
|
||||||
@group(1) @binding(3) var<uniform> 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<workgroup> tile : array<array<vec3<f32>, 128>, 4>;
|
|
||||||
|
|
||||||
@compute @workgroup_size(32, 1, 1)
|
|
||||||
fn main(
|
|
||||||
@builtin(workgroup_id) WorkGroupID : vec3<u32>,
|
|
||||||
@builtin(local_invocation_id) LocalInvocationID : vec3<u32>
|
|
||||||
) {
|
|
||||||
let filterOffset : u32 = (params.filterDim - 1u) / 2u;
|
|
||||||
let dims : vec2<i32> = textureDimensions(inputTex, 0);
|
|
||||||
|
|
||||||
let baseIndex = vec2<i32>(
|
|
||||||
WorkGroupID.xy * vec2<u32>(params.blockDim, 4u) +
|
|
||||||
LocalInvocationID.xy * vec2<u32>(4u, 1u)
|
|
||||||
) - vec2<i32>(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>(i32(c), i32(r));
|
|
||||||
if (flip.value != 0u) {
|
|
||||||
loadIndex = loadIndex.yx;
|
|
||||||
}
|
|
||||||
|
|
||||||
tile[r][4u * LocalInvocationID.x + c] = textureSampleLevel(
|
|
||||||
inputTex,
|
|
||||||
samp,
|
|
||||||
(vec2<f32>(loadIndex) + vec2<f32>(0.25, 0.25)) / vec2<f32>(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>(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<f32> = vec3<f32>(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<f32>(acc, 1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
@group(0) @binding(0) var mySampler : sampler;
|
|
||||||
@group(0) @binding(1) var myTexture : texture_2d<f32>;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) Position : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
|
|
||||||
var pos = array<vec2<f32>, 6>(
|
|
||||||
vec2<f32>( 1.0, 1.0),
|
|
||||||
vec2<f32>( 1.0, -1.0),
|
|
||||||
vec2<f32>(-1.0, -1.0),
|
|
||||||
vec2<f32>( 1.0, 1.0),
|
|
||||||
vec2<f32>(-1.0, -1.0),
|
|
||||||
vec2<f32>(-1.0, 1.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
var uv = array<vec2<f32>, 6>(
|
|
||||||
vec2<f32>(1.0, 0.0),
|
|
||||||
vec2<f32>(1.0, 1.0),
|
|
||||||
vec2<f32>(0.0, 1.0),
|
|
||||||
vec2<f32>(1.0, 0.0),
|
|
||||||
vec2<f32>(0.0, 1.0),
|
|
||||||
vec2<f32>(0.0, 0.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
var output : VertexOutput;
|
|
||||||
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
|
||||||
output.fragUV = uv[VertexIndex];
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn frag_main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
|
|
||||||
return textureSample(myTexture, mySampler, fragUV);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
@fragment fn main(
|
|
||||||
@location(0) fragUV: vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
return fragPosition;
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
@binding(0) @group(0) var<uniform> ubos : array<mat4x4<f32>, 16>;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) position_clip : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn main(@builtin(instance_index) instanceIdx : u32,
|
|
||||||
@location(0) position : vec4<f32>,
|
|
||||||
@location(1) uv : vec2<f32>) -> VertexOutput {
|
|
||||||
var output : VertexOutput;
|
|
||||||
output.position_clip = ubos[instanceIdx] * position;
|
|
||||||
output.fragUV = uv;
|
|
||||||
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit fff6ea92a00c5f6092b896d754a932b8b88149ff
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit c7f20369a142c8a817587da529787597461410a5
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
@group(0) @binding(0) var<storage, read_write> output: array<f32>;
|
|
||||||
|
|
||||||
@compute @workgroup_size(64, 1, 1)
|
|
||||||
fn main(
|
|
||||||
@builtin(global_invocation_id)
|
|
||||||
global_id : vec3<u32>,
|
|
||||||
|
|
||||||
@builtin(local_invocation_id)
|
|
||||||
local_id : vec3<u32>,
|
|
||||||
) {
|
|
||||||
if (global_id.x >= arrayLength(&output)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
output[global_id.x] =
|
|
||||||
f32(global_id.x) * 1000. + f32(local_id.x);
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
@fragment fn main(
|
|
||||||
@location(0) normal: vec3<f32>,
|
|
||||||
@location(1) uv: vec2<f32>,
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
var color = floor((uv * 0.5 + 0.25) * 32) / 32;
|
|
||||||
return vec4<f32>(color, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
@fragment fn main(
|
|
||||||
@location(0) normal: vec3<f32>,
|
|
||||||
@location(1) uv: vec2<f32>,
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(normal / 2 + 0.5, 1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
@group(0) @binding(0)
|
|
||||||
var draw_texture: texture_2d<f32>;
|
|
||||||
@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<f32>;
|
|
||||||
@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<uniform> view: View;
|
|
||||||
|
|
||||||
fn sample_depth(uv: vec2<f32>, x: f32, y: f32) -> f32 {
|
|
||||||
return textureSample(
|
|
||||||
depth_texture,
|
|
||||||
depth_texture_sampler,
|
|
||||||
uv + vec2<f32>(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample_normal(uv: vec2<f32>, x: f32, y: f32) -> vec3<f32> {
|
|
||||||
return textureSample(
|
|
||||||
normal_texture,
|
|
||||||
normal_texture_sampler,
|
|
||||||
uv + vec2<f32>(x * f32(view.pixel_size) / f32(view.width), y * f32(view.pixel_size) / f32(view.height))
|
|
||||||
).xyz;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normal_indicator(uv: vec2<f32>, 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<f32>,
|
|
||||||
@location(0) uv: vec2<f32>
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
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<f32>(1) * step(0.1, normal_diff) * 0.7);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
struct VertexOut {
|
|
||||||
@builtin(position) position_clip: vec4<f32>,
|
|
||||||
@location(0) uv: vec2<f32>
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex fn main(
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@location(1) uv: vec2<f32>
|
|
||||||
) -> VertexOut {
|
|
||||||
var output : VertexOut;
|
|
||||||
output.position_clip = vec4<f32>(position.xy, 0.0, 1.0);
|
|
||||||
output.uv = uv;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
@group(0) @binding(0) var<uniform> ubo: mat4x4<f32>;
|
|
||||||
|
|
||||||
struct VertexOut {
|
|
||||||
@builtin(position) position_clip: vec4<f32>,
|
|
||||||
@location(0) normal: vec3<f32>,
|
|
||||||
@location(1) uv: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex fn main(
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@location(1) normal: vec3<f32>,
|
|
||||||
@location(2) uv: vec2<f32>
|
|
||||||
) -> VertexOut {
|
|
||||||
var output: VertexOut;
|
|
||||||
output.position_clip = vec4<f32>(position, 1) * ubo;
|
|
||||||
output.normal = (vec4<f32>(normal, 0) * ubo).xyz;
|
|
||||||
output.uv = uv;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
@fragment fn main(
|
|
||||||
@location(0) fragUV: vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
return fragPosition;
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
@group(0) @binding(0) var<uniform> ubo : mat4x4<f32>;
|
|
||||||
struct VertexOut {
|
|
||||||
@builtin(position) position_clip : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex fn main(
|
|
||||||
@location(0) position : vec4<f32>,
|
|
||||||
@location(1) uv: vec2<f32>
|
|
||||||
) -> VertexOut {
|
|
||||||
var output : VertexOut;
|
|
||||||
output.position_clip = position * ubo;
|
|
||||||
output.fragUV = uv;
|
|
||||||
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit b5a8404715e6cfc57e66c4a7cc0625e64b3b3c56
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
@group(0) @binding(1) var mySampler: sampler;
|
|
||||||
@group(0) @binding(2) var myTexture: texture_2d<f32>;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn main(@location(0) fragUV: vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
|
|
||||||
return textureSample(myTexture, mySampler, fragUV);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
struct Uniforms {
|
|
||||||
modelViewProjectionMatrix : mat4x4<f32>,
|
|
||||||
};
|
|
||||||
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) Position : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn main(@location(0) position : vec4<f32>,
|
|
||||||
@location(1) uv : vec2<f32>) -> VertexOutput {
|
|
||||||
var output : VertexOutput;
|
|
||||||
output.Position = position * uniforms.modelViewProjectionMatrix;
|
|
||||||
output.fragUV = uv;
|
|
||||||
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
@fragment fn main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
@vertex fn main(
|
|
||||||
@builtin(vertex_index) VertexIndex : u32
|
|
||||||
) -> @builtin(position) vec4<f32> {
|
|
||||||
var pos = array<vec2<f32>, 3>(
|
|
||||||
vec2<f32>( 0.0, 0.5),
|
|
||||||
vec2<f32>(-0.5, -0.5),
|
|
||||||
vec2<f32>( 0.5, -0.5)
|
|
||||||
);
|
|
||||||
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
@fragment fn main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
@vertex fn main(
|
|
||||||
@builtin(vertex_index) VertexIndex : u32
|
|
||||||
) -> @builtin(position) vec4<f32> {
|
|
||||||
var pos = array<vec2<f32>, 3>(
|
|
||||||
vec2<f32>( 0.0, 0.5),
|
|
||||||
vec2<f32>(-0.5, -0.5),
|
|
||||||
vec2<f32>( 0.5, -0.5)
|
|
||||||
);
|
|
||||||
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
@ -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 } },
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
@fragment fn main(
|
|
||||||
@location(0) fragUV: vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
return fragPosition;
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
@group(0) @binding(0) var<uniform> ubo : mat4x4<f32>;
|
|
||||||
struct VertexOut {
|
|
||||||
@builtin(position) position_clip : vec4<f32>,
|
|
||||||
@location(0) fragUV : vec2<f32>,
|
|
||||||
@location(1) fragPosition: vec4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex fn main(
|
|
||||||
@location(0) position : vec4<f32>,
|
|
||||||
@location(1) uv: vec2<f32>
|
|
||||||
) -> VertexOut {
|
|
||||||
var output : VertexOut;
|
|
||||||
output.position_clip = position * ubo;
|
|
||||||
output.fragUV = uv;
|
|
||||||
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue