parent
2b6f3fb1d9
commit
94fbc5d27f
20 changed files with 4505 additions and 0 deletions
107
libs/dusk/test/boids.wgsl
Normal file
107
libs/dusk/test/boids.wgsl
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
const NUM_PARTICLES: u32 = 1500u;
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> params : SimParams;
|
||||
@group(0) @binding(1) var<storage> particlesSrc : Particles;
|
||||
@group(0) @binding(2) var<storage,read_write> particlesDst : 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) global_invocation_id : vec3<u32>) {
|
||||
let index : u32 = global_invocation_id.x;
|
||||
if index >= NUM_PARTICLES {
|
||||
return;
|
||||
}
|
||||
|
||||
var vPos = particlesSrc.particles[index].pos;
|
||||
var vVel = particlesSrc.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 : i32 = 0;
|
||||
var cVelCount : i32 = 0;
|
||||
|
||||
var pos : vec2<f32>;
|
||||
var vel : vec2<f32>;
|
||||
var i : u32 = 0u;
|
||||
loop {
|
||||
if i >= NUM_PARTICLES {
|
||||
break;
|
||||
}
|
||||
if i == index {
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = particlesSrc.particles[i].pos;
|
||||
vel = particlesSrc.particles[i].vel;
|
||||
|
||||
if distance(pos, vPos) < params.rule1Distance {
|
||||
cMass = cMass + pos;
|
||||
cMassCount = cMassCount + 1;
|
||||
}
|
||||
if distance(pos, vPos) < params.rule2Distance {
|
||||
colVel = colVel - (pos - vPos);
|
||||
}
|
||||
if distance(pos, vPos) < params.rule3Distance {
|
||||
cVel = cVel + vel;
|
||||
cVelCount = cVelCount + 1;
|
||||
}
|
||||
|
||||
continuing {
|
||||
i = i + 1u;
|
||||
}
|
||||
}
|
||||
if cMassCount > 0 {
|
||||
cMass = cMass / f32(cMassCount) - vPos;
|
||||
}
|
||||
if cVelCount > 0 {
|
||||
cVel = cVel / f32(cVelCount);
|
||||
}
|
||||
|
||||
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
|
||||
particlesDst.particles[index].pos = vPos;
|
||||
particlesDst.particles[index].vel = vVel;
|
||||
}
|
||||
199
libs/dusk/test/gkurve.wgsl
Normal file
199
libs/dusk/test/gkurve.wgsl
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
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>;
|
||||
|
||||
const wireframe = false;
|
||||
const antialiased = true;
|
||||
const aa_px = 1.0; // pixels to consume for AA
|
||||
const dist_scale_px = 300.0; // TODO: do not hard code
|
||||
|
||||
@fragment fn main(
|
||||
@location(0) uv: vec2<f32>,
|
||||
@interpolate(linear) @location(1) bary_in: vec2<f32>,
|
||||
@interpolate(flat) @location(2) triangle_index: u32,
|
||||
) -> @location(0) vec4<f32> {
|
||||
// Example 1: Visualize barycentric coordinates:
|
||||
// let bary = bary_in;
|
||||
// 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), 0.0 (top-right face)]
|
||||
|
||||
// Example 2: Very simple quadratic bezier
|
||||
// let bary = bary_in;
|
||||
// if (bary.x * bary.x - bary.y) > 0 {
|
||||
// discard;
|
||||
// }
|
||||
// return vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||
|
||||
// Example 3: Render gkurve primitives
|
||||
let inversion = select( 1.0, -1.0, ubos[triangle_index].type_ == 0u || ubos[triangle_index].type_ == 1u);
|
||||
// Texture uvs
|
||||
var correct_uv = uv;
|
||||
correct_uv.y = 1.0 - correct_uv.y;
|
||||
var color = textureSample(myTexture, mySampler, correct_uv) * ubos[triangle_index].blend_color;
|
||||
|
||||
// Curve rendering
|
||||
let border_color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
let border_px = 30.0;
|
||||
let is_semicircle = ubos[triangle_index].type_ == 1u || ubos[triangle_index].type_ == 3u;
|
||||
var result = select(
|
||||
curveColor(bary_in, border_px, border_color, color, inversion, is_semicircle),
|
||||
color,
|
||||
ubos[triangle_index].type_ == 4u, // triangle rendering
|
||||
);
|
||||
|
||||
// Wireframe rendering
|
||||
let wireframe_px = 1.0;
|
||||
let wireframe_color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
|
||||
if (wireframe) {
|
||||
result = wireframeColor(bary_in, wireframe_px, wireframe_color, result);
|
||||
}
|
||||
|
||||
if (result.a == 0.0) { discard; }
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs alpha 'over' blending between two premultiplied-alpha colors.
|
||||
fn alphaOver(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
|
||||
return a + (b * (1.0 - a.a));
|
||||
}
|
||||
|
||||
// Calculates signed distance to a quadratic bézier curve using barycentric coordinates.
|
||||
fn distanceToQuadratic(bary: vec2<f32>) -> f32 {
|
||||
// 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;
|
||||
|
||||
return (bary.x * bary.x - bary.y) / sqrt(fx * fx + fy * fy);
|
||||
}
|
||||
|
||||
// Calculates signed distance to a semicircle using barycentric coordinates.
|
||||
fn distanceToSemicircle(bary: vec2<f32>) -> f32 {
|
||||
let x = abs(((bary.x - 0.5) * 2.0)); // [0.0 left, 1.0 center, 0.0 right]
|
||||
let y = ((bary.x-bary.y) * 4.0); // [2.0 bottom, 0.0 top]
|
||||
let c = x*x + y*y;
|
||||
|
||||
// Gradients
|
||||
let px = dpdx(bary.xy);
|
||||
let py = dpdy(bary.xy);
|
||||
|
||||
// Chain rule
|
||||
let fx = c * px.x - px.y;
|
||||
let fy = c * py.x - py.y;
|
||||
|
||||
let d = (1.0 - (x*x + y*y)) - 0.2;
|
||||
return (-d / 6.0) / sqrt(fx * fx + fy * fy);
|
||||
}
|
||||
|
||||
// Calculates signed distance to the wireframe (i.e. faces) of the triangle using barycentric
|
||||
// coordinates.
|
||||
fn distanceToWireframe(bary: vec2<f32>) -> f32 {
|
||||
let normal = vec3<f32>(
|
||||
bary.y, // distance to right face
|
||||
(bary.x - bary.y) * 2.0, // distance to bottom face
|
||||
1.0 - (((bary.x - bary.y)) + bary.x), // distance to left face
|
||||
);
|
||||
let fw = sqrt(dpdx(normal)*dpdx(normal) + dpdy(normal)*dpdy(normal));
|
||||
let d = normal / fw;
|
||||
return min(min(d.x, d.y), d.z);
|
||||
}
|
||||
|
||||
// Calculates the color of the wireframe, taking into account antialiasing and alpha blending with
|
||||
// the desired background blend color.
|
||||
fn wireframeColor(bary: vec2<f32>, px: f32, color: vec4<f32>, blend_color: vec4<f32>) -> vec4<f32> {
|
||||
let dist = distanceToWireframe(bary);
|
||||
if (antialiased) {
|
||||
let outer = dist;
|
||||
let inner = (px + (aa_px * 2.0)) - dist;
|
||||
let in_wireframe = outer >= 0.0 && inner >= 0.0;
|
||||
if (in_wireframe) {
|
||||
// Note: If this is the outer edge of the wireframe, we do not want to perform alpha
|
||||
// blending with the background blend color, since it is an antialiased edge and should
|
||||
// be transparent. However, if it is the internal edge of the wireframe, we do want to
|
||||
// perform alpha blending as it should be an overlay, not transparent.
|
||||
let is_outer_edge = outer < inner;
|
||||
if (is_outer_edge) {
|
||||
let alpha = smoothstep(0.0, 1.0, outer*(1.0 / aa_px));
|
||||
return vec4<f32>((color.rgb/color.a)*alpha, alpha);
|
||||
} else {
|
||||
let aa_inner = inner - aa_px;
|
||||
let alpha = smoothstep(0.0, 1.0, aa_inner*(1.0 / aa_px));
|
||||
let wireframe_color = vec4<f32>((color.rgb/color.a)*alpha, alpha);
|
||||
return alphaOver(wireframe_color, blend_color);
|
||||
}
|
||||
}
|
||||
return blend_color;
|
||||
} else {
|
||||
// If we're at the edge use the wireframe color, otherwise use the background blend_color.
|
||||
return select(blend_color, color, (px - dist) >= 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates the color for a curve, taking into account antialiasing and alpha blending with
|
||||
// the desired background blend color.
|
||||
//
|
||||
// inversion: concave (-1.0) or convex (1.0)
|
||||
// is_semicircle: quadratic bezier (false) or semicircle (true)
|
||||
fn curveColor(
|
||||
bary: vec2<f32>,
|
||||
border_px: f32,
|
||||
border_color: vec4<f32>,
|
||||
blend_color: vec4<f32>,
|
||||
inversion: f32,
|
||||
is_semicircle: bool,
|
||||
) -> vec4<f32> {
|
||||
let dist = select(
|
||||
distanceToQuadratic(bary),
|
||||
distanceToSemicircle(bary),
|
||||
is_semicircle,
|
||||
) * inversion;
|
||||
let is_inverted = (inversion + 1.0) / 2.0; // 1.0 if inverted, 0.0 otherwise
|
||||
|
||||
if (antialiased) {
|
||||
let outer = dist + ((border_px + (aa_px * 2.0)) * is_inverted); // bottom
|
||||
let inner = ((border_px + (aa_px * 2.0)) * (1.0-is_inverted)) - dist; // top
|
||||
let in_border = outer >= 0.0 && inner >= 0.0;
|
||||
if (in_border) {
|
||||
// Note: If this is the outer edge of the curve, we do not want to perform alpha
|
||||
// blending with the background blend color, since it is an antialiased edge and should
|
||||
// be transparent. However, if it is the internal edge of the curve, we do want to
|
||||
// perform alpha blending as it should be an overlay, not transparent.
|
||||
let is_outer_edge = outer < inner;
|
||||
if (is_outer_edge) {
|
||||
let aa_outer = outer - (aa_px * is_inverted);
|
||||
let alpha = smoothstep(0.0, 1.0, aa_outer*(1.0 / aa_px));
|
||||
return vec4<f32>((border_color.rgb/border_color.a)*alpha, alpha);
|
||||
} else {
|
||||
let aa_inner = inner - (aa_px * (1.0 - is_inverted));
|
||||
let alpha = smoothstep(0.0, 1.0, aa_inner*(1.0 / aa_px));
|
||||
let new_border_color = vec4<f32>((border_color.rgb/border_color.a)*alpha, alpha);
|
||||
return alphaOver(new_border_color, blend_color);
|
||||
}
|
||||
return border_color;
|
||||
} else if (outer >= 0.0) {
|
||||
return blend_color;
|
||||
} else {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
} else {
|
||||
let outer = dist + (border_px * is_inverted);
|
||||
let inner = (border_px * (1.0-is_inverted)) - dist;
|
||||
let in_border = outer >= 0.0 && inner >= 0.0;
|
||||
if (in_border) {
|
||||
return border_color;
|
||||
} else if (outer >= 0.0) {
|
||||
return blend_color;
|
||||
} else {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
170
libs/dusk/test/main.zig
Normal file
170
libs/dusk/test/main.zig
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
const std = @import("std");
|
||||
const dusk = @import("dusk");
|
||||
const expect = std.testing.expect;
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
fn sdkPath(comptime suffix: []const u8) []const u8 {
|
||||
if (suffix[0] != '/') @compileError("suffix must be an absolute path");
|
||||
return comptime blk: {
|
||||
const root_dir = std.fs.path.dirname(@src().file) orelse ".";
|
||||
break :blk root_dir ++ suffix;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: move this to cli/main.zig
|
||||
pub fn printErrors(errors: []dusk.ErrorMsg, source: []const u8, file_path: ?[]const u8) !void {
|
||||
var bw = std.io.bufferedWriter(std.io.getStdErr().writer());
|
||||
const b = bw.writer();
|
||||
const term = std.debug.TTY.Config{ .escape_codes = {} };
|
||||
|
||||
for (errors) |*err| {
|
||||
defer err.deinit(allocator);
|
||||
|
||||
const loc_extra = err.loc.extraInfo(source);
|
||||
|
||||
// 'file:line:column error: <MSG>'
|
||||
try term.setColor(b, .Bold);
|
||||
try b.print("{?s}:{d}:{d} ", .{ file_path, loc_extra.line, loc_extra.col });
|
||||
try term.setColor(b, .Red);
|
||||
try b.writeAll("error: ");
|
||||
try term.setColor(b, .Reset);
|
||||
try term.setColor(b, .Bold);
|
||||
try b.writeAll(err.msg);
|
||||
try b.writeByte('\n');
|
||||
|
||||
try printCode(b, term, source, err.loc);
|
||||
|
||||
// note
|
||||
if (err.note) |note| {
|
||||
if (note.loc) |note_loc| {
|
||||
const note_loc_extra = note_loc.extraInfo(source);
|
||||
|
||||
try term.setColor(b, .Reset);
|
||||
try term.setColor(b, .Bold);
|
||||
try b.print("{?s}:{d}:{d} ", .{ file_path, note_loc_extra.line, note_loc_extra.col });
|
||||
}
|
||||
try term.setColor(b, .Cyan);
|
||||
try b.writeAll("note: ");
|
||||
|
||||
try term.setColor(b, .Reset);
|
||||
try term.setColor(b, .Bold);
|
||||
try b.writeAll(note.msg);
|
||||
try b.writeByte('\n');
|
||||
|
||||
if (note.loc) |note_loc| {
|
||||
try printCode(b, term, source, note_loc);
|
||||
}
|
||||
}
|
||||
|
||||
try term.setColor(b, .Reset);
|
||||
}
|
||||
try bw.flush();
|
||||
}
|
||||
|
||||
fn printCode(writer: anytype, term: std.debug.TTY.Config, source: []const u8, loc: dusk.Token.Loc) !void {
|
||||
const loc_extra = loc.extraInfo(source);
|
||||
try term.setColor(writer, .Dim);
|
||||
try writer.print("{d} │ ", .{loc_extra.line});
|
||||
try term.setColor(writer, .Reset);
|
||||
try writer.writeAll(source[loc_extra.line_start..loc.start]);
|
||||
try term.setColor(writer, .Green);
|
||||
try writer.writeAll(source[loc.start..loc.end]);
|
||||
try term.setColor(writer, .Reset);
|
||||
try writer.writeAll(source[loc.end..loc_extra.line_end]);
|
||||
try writer.writeByte('\n');
|
||||
|
||||
// location pointer
|
||||
const line_number_len = (std.math.log10(loc_extra.line) + 1) + 3;
|
||||
try writer.writeByteNTimes(
|
||||
' ',
|
||||
line_number_len + (loc_extra.col - 1),
|
||||
);
|
||||
try term.setColor(writer, .Bold);
|
||||
try term.setColor(writer, .Green);
|
||||
try writer.writeByte('^');
|
||||
try writer.writeByteNTimes('~', loc.end - loc.start - 1);
|
||||
try writer.writeByte('\n');
|
||||
}
|
||||
|
||||
fn expectTree(source: [:0]const u8) !dusk.Ast {
|
||||
var res = try dusk.Ast.parse(allocator, source);
|
||||
switch (res) {
|
||||
.tree => |*tree| {
|
||||
errdefer tree.deinit(allocator);
|
||||
if (try tree.analyse(allocator)) |errors| {
|
||||
try printErrors(errors, source, null);
|
||||
allocator.free(errors);
|
||||
return error.Analysing;
|
||||
}
|
||||
return tree.*;
|
||||
},
|
||||
.errors => |err_msgs| {
|
||||
try printErrors(err_msgs, source, null);
|
||||
allocator.free(err_msgs);
|
||||
return error.Parsing;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test "empty" {
|
||||
const source = "";
|
||||
var tree = try expectTree(source);
|
||||
defer tree.deinit(allocator);
|
||||
}
|
||||
|
||||
test "boids" {
|
||||
const source = @embedFile("boids.wgsl");
|
||||
var tree = try expectTree(source);
|
||||
defer tree.deinit(allocator);
|
||||
}
|
||||
|
||||
test "gkurve" {
|
||||
if (true) return error.SkipZigTest;
|
||||
|
||||
const source = @embedFile("gkurve.wgsl");
|
||||
var tree = try expectTree(source);
|
||||
defer tree.deinit(allocator);
|
||||
}
|
||||
|
||||
test "variable & expressions" {
|
||||
const source = "var expr = 1 + 5 + 2 * 3 > 6 >> 7;";
|
||||
|
||||
var tree = try expectTree(source);
|
||||
defer tree.deinit(allocator);
|
||||
|
||||
const root_node = 0;
|
||||
try expect(tree.nodeLHS(root_node) + 1 == tree.nodeRHS(root_node));
|
||||
|
||||
const variable = tree.spanToList(root_node)[0];
|
||||
const variable_name = tree.tokenLoc(tree.extraData(dusk.Ast.Node.GlobalVarDecl, tree.nodeLHS(variable)).name);
|
||||
try expect(std.mem.eql(u8, "expr", variable_name.slice(source)));
|
||||
try expect(tree.nodeTag(variable) == .global_variable);
|
||||
try expect(tree.tokenTag(tree.nodeToken(variable)) == .k_var);
|
||||
|
||||
const expr = tree.nodeRHS(variable);
|
||||
try expect(tree.nodeTag(expr) == .greater);
|
||||
|
||||
const @"1 + 5 + 2 * 3" = tree.nodeLHS(expr);
|
||||
try expect(tree.nodeTag(@"1 + 5 + 2 * 3") == .add);
|
||||
|
||||
const @"1 + 5" = tree.nodeLHS(@"1 + 5 + 2 * 3");
|
||||
try expect(tree.nodeTag(@"1 + 5") == .add);
|
||||
|
||||
const @"1" = tree.nodeLHS(@"1 + 5");
|
||||
try expect(tree.nodeTag(@"1") == .number_literal);
|
||||
|
||||
const @"5" = tree.nodeRHS(@"1 + 5");
|
||||
try expect(tree.nodeTag(@"5") == .number_literal);
|
||||
|
||||
const @"2 * 3" = tree.nodeRHS(@"1 + 5 + 2 * 3");
|
||||
try expect(tree.nodeTag(@"2 * 3") == .mul);
|
||||
|
||||
const @"6 >> 7" = tree.nodeRHS(expr);
|
||||
try expect(tree.nodeTag(@"6 >> 7") == .shift_right);
|
||||
|
||||
const @"6" = tree.nodeLHS(@"6 >> 7");
|
||||
try expect(tree.nodeTag(@"6") == .number_literal);
|
||||
|
||||
const @"7" = tree.nodeRHS(@"6 >> 7");
|
||||
try expect(tree.nodeTag(@"7") == .number_literal);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue