From a1daf399a328fad6c13980b3c36d84a2cb4b2cda Mon Sep 17 00:00:00 2001 From: PiergiorgioZagaria Date: Sun, 8 May 2022 19:13:40 +0200 Subject: [PATCH] examples: created gkurve example --- build.zig | 1 + examples/gkurve/frag.wgsl | 123 ++++++++++++++++++ examples/gkurve/main.zig | 258 ++++++++++++++++++++++++++++++++++++++ examples/gkurve/vert.wgsl | 22 ++++ 4 files changed, 404 insertions(+) create mode 100755 examples/gkurve/frag.wgsl create mode 100644 examples/gkurve/main.zig create mode 100644 examples/gkurve/vert.wgsl diff --git a/build.zig b/build.zig index 562e334a..940de690 100644 --- a/build.zig +++ b/build.zig @@ -35,6 +35,7 @@ pub fn build(b: *std.build.Builder) void { .{ .name = "rotating-cube", .packages = &[_]Pkg{Packages.zmath} }, .{ .name = "two-cubes", .packages = &[_]Pkg{Packages.zmath} }, .{ .name = "instanced-cube", .packages = &[_]Pkg{Packages.zmath} }, + .{ .name = "gkurve", .packages = &[_]Pkg{Packages.zmath} }, .{ .name = "advanced-gen-texture-light", .packages = &[_]Pkg{Packages.zmath} }, .{ .name = "textured-cube", .packages = &[_]Pkg{ Packages.zmath, Packages.zigimg } }, .{ .name = "fractal-cube", .packages = &[_]Pkg{Packages.zmath} }, diff --git a/examples/gkurve/frag.wgsl b/examples/gkurve/frag.wgsl new file mode 100755 index 00000000..3ab725f8 --- /dev/null +++ b/examples/gkurve/frag.wgsl @@ -0,0 +1,123 @@ +//! Ported from https://www.shadertoy.com/view/ltXSDB + +// Signed Distance to a Quadratic Bezier Curve +// - Adam Simmons (@adamjsimmons) 2015 +// +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License +// +// Inspired by http://www.pouet.net/topic.php?which=9119 +// and various shaders by iq, T21, and demofox +// +// I needed the -signed- distance to a quadratic bezier +// curve but couldn't find any examples online that +// were both fast and precise. This is my solution. +// +// v1 - Initial release +// v2 - Faster and more robust sign computation +// + +struct FragUniform { + points: array, 3>, + type_: u32, +} +@binding(1) @group(0) var ubos : array; + +// Test if point p crosses line (a, b), returns sign of result +fn testCross(a:vec2, b:vec2, p:vec2) -> f32{ + return sign((b.y - a.y) * (p.x - a.x) - (b.x - a.x) * (p.y - a.y)); +} + +// Determine which side we're on (using barycentric parameterization) +fn signBezier(A: vec2, B: vec2, C: vec2, p:vec2) -> f32 { + let a = C - A; + let b = B - A; + let c = p - A; + let bary = vec2(c.x * b.y - b.x * c.y, a.x * c.y - c.x * a.y) / (a.x * b.y - b.x * a.y); + let d = vec2(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y; + return mix(sign(d.x * d.x - d.y), mix(-1.0, 1.0, + step(testCross(A, B, p) * testCross(B, C, p), 0.0)), + step((d.x - d.y), 0.0)) * testCross(A, C, B); +} + +// Solve cubic equation for roots +fn solveCubic(a: f32, b: f32, c: f32) -> vec3 { + let p = b - a * a / 3.0; + let p3 = p * p * p; + let q = a * (2.0 * a * a - 9.0 * b) / 27.0 + c; + let d = q * q + 4.0 * p3 / 27.0; + let offset = -a / 3.0; + if(d >= 0.0) { + let z = sqrt(d); + let x = (vec2(z, -z) - q) / 2.0; + let uv = sign(x) * pow(abs(x), vec2(1.0 / 3.0)); + return vec3(offset + uv.x + uv.y); + } + let v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0; + let m = cos(v); + let n = sin(v) * 1.732050808; + return vec3(m + m, -n - m, n - m) * sqrt(-p / 3.0) + offset; +} + +// Find the signed distance from a point to a bezier curve +fn sdBezier(A: vec2, B_: vec2,C: vec2,p: vec2) -> f32{ + let B = mix(B_ + vec2(1e-4), B_, abs(sign(B_ * 2.0 - A - C))); + + let a = B - A; + let b = A - B * 2.0 + C; + let c = a * 2.0; + let d = A - p; + + let k = vec3(3.0 * dot(a,b), 2.0 * dot(a,a) + dot(d,b), dot(d,a)) / dot(b,b); + let t = clamp(solveCubic(k.x, k.y, k.z), vec3(0.0), vec3(1.0)); + + var pos = A + (c + b * t.x) * t.x; + var dis = length(pos - p); + + pos = A + (c + b * t.y) * t.y; + dis = min(dis, length(pos - p)); + pos = A + (c + b * t.z) * t.z; + dis = min(dis, length(pos - p)); + + return dis * signBezier(A, B, C, p); +} + + +@stage(fragment) fn main( + @location(0) uv : vec2, + @interpolate(flat) @location(1) instance_index: u32, +) -> @location(0) vec4 { + var col = vec4(0.0); + + let p = uv; + + // Define the control points of our curve + var A = ubos[instance_index].points[0].xy; + var B = ubos[instance_index].points[1].xy; + var C = ubos[instance_index].points[2].xy; + + if(ubos[instance_index].type_ == 2u){ + let tmp = A; + A.x = C.x; + A.y = B.y; + C.y = B.y; + B.y = tmp.y; + C.x = tmp.x; + } + + // Render the control points + // var d = min(distance(p, A),min(distance(p, C),distance(p,B))); + // if (d < 0.04) { + // return vec4(1.0 - smoothstep(0.025, 0.034, d)); + // } + + // Get the signed distance to bezier curve + let d = sdBezier(A, B, C, p); + let tex_col = vec4(0.0,1.0,0.0,0.0); + // Visualize the distance field using iq's orange/blue scheme + if (ubos[instance_index].type_ == 1u){ + col = tex_col; + }else{ + col = sign(d) * tex_col; + } + return col; +} diff --git a/examples/gkurve/main.zig b/examples/gkurve/main.zig new file mode 100644 index 00000000..074aace9 --- /dev/null +++ b/examples/gkurve/main.zig @@ -0,0 +1,258 @@ +// TODO: +// - add texture and sampler. +// - find a way to use dynamic arrays in wgsl for ubos +// - understand how to move the triangles via matrix multplication + +const std = @import("std"); +const mach = @import("mach"); +const gpu = @import("gpu"); +const zm = @import("zmath"); +const glfw = @import("glfw"); + +pub const Vertex = struct { + pos: @Vector(4, f32), + uv: @Vector(2, f32), +}; +// Simple triangle +pub const vertices = [_]Vertex{ + .{ .pos = .{ 0, 0.5, 0, 1 }, .uv = .{ 0.5, 1 } }, + .{ .pos = .{ -0.5, -0.5, 0, 1 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 0.5, -0.5, 0, 1 }, .uv = .{ 1, 0 } }, +}; + +// The uniform read by the vertex shader, it contains the matrix +// that will move vertices +const VertexUniform = struct { + mat: zm.Mat, +}; + +// The uniform read by the fragment shader, the points are used +// to calculate the bezier curve, and more or less coincide with uvs +// (Vec4 for alignment) +const FragUniform = struct { + points: [3]@Vector(4, f32), + // TODO use an enum? Remember that it will be casted to u32 in wgsl + type: u32, +}; +// TODO texture and sampler, create buffers and use an index field +// in FragUniform to tell which texture to read + +// Hard-coded, if you change it remember to change it in the shaders +const num_instances = 3; + +const App = @This(); + +pipeline: gpu.RenderPipeline, +queue: gpu.Queue, +vertex_buffer: gpu.Buffer, +vertex_uniform_buffer: gpu.Buffer, +frag_uniform_buffer: gpu.Buffer, +bind_group: gpu.BindGroup, + +pub fn init(app: *App, engine: *mach.Engine) !void { + engine.core.internal.window.setKeyCallback(struct { + fn callback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void { + _ = scancode; + _ = mods; + if (action == .press) { + switch (key) { + .space => window.setShouldClose(true), + else => {}, + } + } + } + }.callback); + try engine.core.internal.window.setSizeLimits(.{ .width = 20, .height = 20 }, .{ .width = null, .height = null }); + + const vs_module = engine.gpu_driver.device.createShaderModule(&.{ + .label = "my vertex shader", + .code = .{ .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{ + .array_stride = @sizeOf(Vertex), + .step_mode = .vertex, + .attribute_count = vertex_attributes.len, + .attributes = &vertex_attributes, + }; + + const fs_module = engine.gpu_driver.device.createShaderModule(&.{ + .label = "my fragment shader", + .code = .{ .wgsl = @embedFile("frag.wgsl") }, + }); + + // Fragment state + const color_target = gpu.ColorTargetState{ + .format = engine.gpu_driver.swap_chain_format, + .blend = null, + .write_mask = gpu.ColorWriteMask.all, + }; + const fragment = gpu.FragmentState{ + .module = fs_module, + .entry_point = "main", + .targets = &.{color_target}, + .constants = null, + }; + + const vbgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, true, 0); + const fbgle = gpu.BindGroupLayout.Entry.buffer(1, .{ .fragment = true }, .uniform, true, 0); + const bgl = engine.gpu_driver.device.createBindGroupLayout( + &gpu.BindGroupLayout.Descriptor{ + .entries = &.{ vbgle, fbgle }, + }, + ); + const bind_group_layouts = [_]gpu.BindGroupLayout{bgl}; + const pipeline_layout = engine.gpu_driver.device.createPipelineLayout(&.{ + .bind_group_layouts = &bind_group_layouts, + }); + + const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ + .fragment = &fragment, + .layout = pipeline_layout, + .depth_stencil = null, + .vertex = .{ + .module = vs_module, + .entry_point = "main", + .buffers = &.{vertex_buffer_layout}, + }, + .multisample = .{ + .count = 1, + .mask = 0xFFFFFFFF, + .alpha_to_coverage_enabled = false, + }, + .primitive = .{ + .front_face = .ccw, + .cull_mode = .none, + .topology = .triangle_list, + .strip_index_format = .none, + }, + }; + const vertex_buffer = engine.gpu_driver.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 vertex_uniform_buffer = engine.gpu_driver.device.createBuffer(&.{ + .usage = .{ .copy_dst = true, .uniform = true }, + .size = @sizeOf(VertexUniform) * num_instances, + .mapped_at_creation = false, + }); + + const frag_uniform_buffer = engine.gpu_driver.device.createBuffer(&.{ + .usage = .{ .uniform = true }, + .size = @sizeOf(FragUniform) * num_instances, + .mapped_at_creation = true, + }); + var frag_uniform_mapped = frag_uniform_buffer.getMappedRange(FragUniform, 0, num_instances); + const tmp_frag_ubo = [_]FragUniform{ + .{ + // The points correspond to the left point, middle point, right point (when viewed regularly) + // in UV coordinates + .points = [_]@Vector(4, f32){ + .{ 0, 0, 0, 0 }, + .{ 0.5, 1, 0, 0 }, + .{ 1, 0, 0, 0 }, + }, + .type = 1, + }, + .{ + .points = [_]@Vector(4, f32){ + .{ 0, 0, 0, 0 }, + .{ 0.5, 1, 0, 0 }, + .{ 1, 0, 0, 0 }, + }, + .type = 0, + }, + .{ + .points = [_]@Vector(4, f32){ + .{ 0, 0, 0, 0 }, + .{ 0.5, 1, 0, 0 }, + .{ 1, 0, 0, 0 }, + }, + .type = 2, + }, + }; + std.mem.copy(FragUniform, frag_uniform_mapped, &tmp_frag_ubo); + frag_uniform_buffer.unmap(); + + const bind_group = engine.gpu_driver.device.createBindGroup( + &gpu.BindGroup.Descriptor{ + .layout = bgl, + .entries = &.{ + gpu.BindGroup.Entry.buffer(0, vertex_uniform_buffer, 0, @sizeOf(VertexUniform) * num_instances), + gpu.BindGroup.Entry.buffer(1, frag_uniform_buffer, 0, @sizeOf(FragUniform) * num_instances), + }, + }, + ); + + app.pipeline = engine.gpu_driver.device.createRenderPipeline(&pipeline_descriptor); + app.queue = engine.gpu_driver.device.getQueue(); + app.vertex_buffer = vertex_buffer; + app.vertex_uniform_buffer = vertex_uniform_buffer; + app.frag_uniform_buffer = frag_uniform_buffer; + app.bind_group = bind_group; + + vs_module.release(); + fs_module.release(); + pipeline_layout.release(); + bgl.release(); +} + +pub fn deinit(app: *App, _: *mach.Engine) void { + app.vertex_buffer.release(); + app.vertex_uniform_buffer.release(); + app.frag_uniform_buffer.release(); + app.bind_group.release(); +} + +pub fn update(app: *App, engine: *mach.Engine) !bool { + const back_buffer_view = engine.gpu_driver.swap_chain.?.getCurrentTextureView(); + const color_attachment = gpu.RenderPassColorAttachment{ + .view = back_buffer_view, + .resolve_target = null, + .clear_value = std.mem.zeroes(gpu.Color), + .load_op = .clear, + .store_op = .store, + }; + + const encoder = engine.gpu_driver.device.createCommandEncoder(null); + const render_pass_info = gpu.RenderPassEncoder.Descriptor{ + .color_attachments = &.{color_attachment}, + }; + + { + // TODO: + // Use better positioning system + const ubos = [_]VertexUniform{ + .{ .mat = zm.translation(0.5, 0.5, 0) }, + .{ .mat = zm.translation(-0.5, 0, 0) }, + .{ .mat = zm.translation(0.5, -0.5, 0) }, + }; + encoder.writeBuffer(app.vertex_uniform_buffer, 0, VertexUniform, &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, 0 }); + pass.draw(vertices.len, num_instances, 0, 0); + pass.end(); + pass.release(); + + var command = encoder.finish(null); + encoder.release(); + + app.queue.submit(&.{command}); + command.release(); + engine.gpu_driver.swap_chain.?.present(); + back_buffer_view.release(); + + return true; +} diff --git a/examples/gkurve/vert.wgsl b/examples/gkurve/vert.wgsl new file mode 100644 index 00000000..81108b12 --- /dev/null +++ b/examples/gkurve/vert.wgsl @@ -0,0 +1,22 @@ +struct VertexUniform { + matrix: mat4x4, +} +@binding(0) @group(0) var ubos : array; + +struct VertexOut { + @builtin(position) position_clip : vec4, + @location(0) frag_uv : vec2, + @interpolate(flat) @location(1) instance_index: u32, +} + +@stage(vertex) fn main( + @builtin(instance_index) instanceIdx : u32, + @location(0) position: vec4, + @location(1) uv: vec2, +) -> VertexOut { + var output : VertexOut; + output.position_clip = ubos[instanceIdx].matrix * position; + output.frag_uv = uv; + output.instance_index = instanceIdx; + return output; +}