diff --git a/build.zig b/build.zig index 8b9ad378..1ca2b28d 100644 --- a/build.zig +++ b/build.zig @@ -52,6 +52,20 @@ pub fn build(b: *std.build.Builder) void { const example_run_step = b.step("run-example-" ++ example.name, "Run the example"); example_run_step.dependOn(&example_run_cmd.step); } + + const shaderexp_exe = b.addExecutable("shaderexp", "shaderexp/main.zig"); + shaderexp_exe.setTarget(target); + shaderexp_exe.setBuildMode(mode); + shaderexp_exe.addPackage(pkg); + shaderexp_exe.addPackage(gpu.pkg); + shaderexp_exe.addPackage(glfw.pkg); + link(b, shaderexp_exe, options); + shaderexp_exe.install(); + + const shaderexp_run_cmd = shaderexp_exe.run(); + shaderexp_run_cmd.step.dependOn(b.getInstallStep()); + const shaderexp_run_step = b.step("run-shaderexp", "Run shaderexp"); + shaderexp_run_step.dependOn(&shaderexp_run_cmd.step); } pub const Options = struct { diff --git a/shaderexp/README.md b/shaderexp/README.md new file mode 100644 index 00000000..e1beee4b --- /dev/null +++ b/shaderexp/README.md @@ -0,0 +1,8 @@ +# Shaderexp + +This is an executable for testing wgsl shaders on the fly. +Build it and run it with: +`zig build run-shaderexp` + +Then modify shaderexp/frag.wgsl and save. The window will update and use the new fragment shader. +If errors occur, the window will show a black_screen and an error message will be written to stdout. diff --git a/shaderexp/black_screen_frag.wgsl b/shaderexp/black_screen_frag.wgsl new file mode 100755 index 00000000..bc6f45d8 --- /dev/null +++ b/shaderexp/black_screen_frag.wgsl @@ -0,0 +1,11 @@ +struct UniformBufferObject { + time: f32, + resolution: vec2, +} +@group(0) @binding(0) var ubo : UniformBufferObject; + +@stage(fragment) fn main( + @location(0) uv : vec2 +) -> @location(0) vec4 { + return vec4( 0.0, 0.0, 0.0, 1.0); +} diff --git a/shaderexp/example-shaders/mandelbrot.wgsl b/shaderexp/example-shaders/mandelbrot.wgsl new file mode 100755 index 00000000..c2058153 --- /dev/null +++ b/shaderexp/example-shaders/mandelbrot.wgsl @@ -0,0 +1,38 @@ +struct UniformBufferObject { + resolution: vec2, + time: f32, +} +@group(0) @binding(0) var ubo : UniformBufferObject; + +@stage(fragment) fn main( + @location(0) uv : vec2 +) -> @location(0) vec4 { + let aspect = ubo.resolution / min(ubo.resolution.x,ubo.resolution.y); + let translated_uv = (uv - vec2(0.5,0.5)) * aspect * 2.0; + let col = f32(mandel(translated_uv)) / 100.0; + + return vec4(vec3(col), 1.0); +} + +fn mandel(uv: vec2) -> i32{ + let zoom = 1.0; + let center_position = vec2(0.5,0.0); + let mapped_point = uv * zoom - center_position; + var z = mapped_point; + var tmp:f32; + var i:i32 = 0; + var found = false; + var res = 0; + loop { + if (i >= 100){ + break; + } + tmp = z.x; + z.x = z.x * z.x - z.y * z.y + mapped_point.x; + z.y = 2. * tmp * z.y + mapped_point.y; + found = found || (z.x * z.x + z.y * z.y > 16.); + res = res + 1 * i32(!found); + i = i + 1; + } + return res; +} diff --git a/shaderexp/example-shaders/ray_marching.wgsl b/shaderexp/example-shaders/ray_marching.wgsl new file mode 100755 index 00000000..e783ec95 --- /dev/null +++ b/shaderexp/example-shaders/ray_marching.wgsl @@ -0,0 +1,98 @@ +// A slight modification / translation of https://www.shadertoy.com/view/XlGBW3 +// to WGSL. +// License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +struct UniformBufferObject { + resolution: vec2, + time: f32, +} +@group(0) @binding(0) var ubo : UniformBufferObject; + +fn getDist(p:vec3) -> f32{ + let dist_from_center:f32 = 2.*sin(ubo.time * 3.); + let rotation_speed:f32 = 6.; + + let sphere1 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 0. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 0. * 2. / 3.),1.); + let sphere2 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 1. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 1. * 2. / 3.),1.); + let sphere3 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 2. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 2. * 2. / 3.),1.); + + let sphere1_dist:f32 = length(p - sphere1.xyz) - sphere1.w; + let sphere2_dist:f32 = length(p - sphere2.xyz) - sphere2.w; + let sphere3_dist:f32 = length(p - sphere3.xyz) - sphere3.w; + let plane_dist = p.y; + + return min(min(min(sphere1_dist,sphere2_dist),sphere3_dist),plane_dist); +} + +fn rayMarch(ro:vec3, rd:vec3) -> f32{ + let MAX_STEPS:i32 = 100; + let MAX_DIST:f32 = 100.0; + let SURF_DIST:f32 = 0.01; + var d:f32 = 0.0; + + var i: i32 = 0; + loop { + if(i >= MAX_STEPS){ + break; + } + + let p = ro + rd * d; + let ds = getDist(p); + d = d + ds; + if(d > MAX_DIST || ds <= SURF_DIST){ + break; + } + + i = i + 1; + } + return d; +} + +fn getNormal(p:vec3) -> vec3{ + let d = getDist(p); + let e = vec2(0.1,0.0); + + // We can find the normal using the points around the hit point + let n = d - vec3( + getDist(p-e.xyy), + getDist(p-e.yxy), + getDist(p-e.yyx) + ); + + return normalize(n); +} + +fn getLight(p:vec3) -> f32{ + let SURF_DIST:f32 = .01; + + let light_pos = vec3(0.,5.,0.); + let l = normalize(light_pos - p) * 1.; + let n = getNormal(p); + + var dif = clamp(dot(n,l),.0,1.); + + let d = rayMarch(p + n * SURF_DIST * 2.,l); + if(d +) -> @location(0) vec4 { + let aspect = ubo.resolution / min(ubo.resolution.x,ubo.resolution.y); + let tmp_uv = (uv - vec2(0.5,0.5)) * aspect * 2.0; + var col = vec3(0.0); + + let r_origin = vec3(4.0,3.,.0); + let r_dir = normalize(vec3(-1.0,tmp_uv.y,tmp_uv.x)); + let d = rayMarch(r_origin,r_dir); + col = vec3(d / 8.); + let p = r_origin + r_dir * d; + let diff = getLight(p); + + col = vec3(diff , 0.,0.); + return vec4(col,0.0); +} diff --git a/shaderexp/example-shaders/test_shader.wgsl b/shaderexp/example-shaders/test_shader.wgsl new file mode 100755 index 00000000..b8476ccd --- /dev/null +++ b/shaderexp/example-shaders/test_shader.wgsl @@ -0,0 +1,40 @@ +struct UniformBufferObject { + resolution: vec2, + time: f32, +} +@group(0) @binding(0) var ubo : UniformBufferObject; + +@stage(fragment) fn main( + @location(0) uv : vec2 +) -> @location(0) vec4 { + let aspect = ubo.resolution.xy / ubo.resolution.y; + let translated_uv = (uv - vec2(0.5,0.5)) * 2.0 * aspect; + let freq:f32 = 5.0; + let speed: f32 = 5.0; + let h = (sin(freq * length(translated_uv) + speed * ubo.time) + 1.0) / 2.0; + + let h_off = 20.0; + return vec4(hsl_to_rgb(h * (360.0 - h_off * 2.0) + h_off ,0.7,0.5),1.0); +} + +// 0 ≤ H < 360, 0 ≤ S ≤ 1 and 0 ≤ L ≤ 1 +fn hsl_to_rgb(h:f32,s:f32,l:f32) -> vec3 { + let tmp_h = h % 360.0; + let c = (1.0 - abs(2.0 * l - 1.0)) * s; + let x = c * (1.0 - abs((tmp_h / 60.0) % 2.0 - 1.0)); + let m = l - c / 2.0; + + let case_1 = vec3(c ,x ,0.0); + let case_2 = vec3(x ,c ,0.0); + let case_3 = vec3(0.0,c ,x); + let case_4 = vec3(0.0,x ,c); + let case_5 = vec3(x ,0.0,c); + let case_6 = vec3(c ,0.0,x); + + return case_1 * f32(tmp_h < 60.0 && tmp_h >= 0.0) + + case_2 * f32(tmp_h < 120.0 && tmp_h >= 60.0) + + case_3 * f32(tmp_h < 180.0 && tmp_h >= 120.0) + + case_4 * f32(tmp_h < 240.0 && tmp_h >= 180.0) + + case_5 * f32(tmp_h < 300.0 && tmp_h >= 240.0) + + case_6 * f32(tmp_h < 360.0 && tmp_h >= 300.0) + vec3(m); +} diff --git a/shaderexp/frag.wgsl b/shaderexp/frag.wgsl new file mode 100755 index 00000000..e13d4cbe --- /dev/null +++ b/shaderexp/frag.wgsl @@ -0,0 +1,94 @@ +struct UniformBufferObject { + resolution: vec2, + time: f32, +} +@group(0) @binding(0) var ubo : UniformBufferObject; + +fn getDist(p:vec3) -> f32{ + let dist_from_center:f32 = 2.*sin(ubo.time * 3.); + let rotation_speed:f32 = 6.; + + let sphere1 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 0. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 0. * 2. / 3.),1.); + let sphere2 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 1. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 1. * 2. / 3.),1.); + let sphere3 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 2. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 2. * 2. / 3.),1.); + + let sphere1_dist:f32 = length(p - sphere1.xyz) - sphere1.w; + let sphere2_dist:f32 = length(p - sphere2.xyz) - sphere2.w; + let sphere3_dist:f32 = length(p - sphere3.xyz) - sphere3.w; + let plane_dist = p.y; + + return min(min(min(sphere1_dist,sphere2_dist),sphere3_dist),plane_dist); +} + +fn rayMarch(ro:vec3, rd:vec3) -> f32{ + let MAX_STEPS:i32 = 100; + let MAX_DIST:f32 = 100.0; + let SURF_DIST:f32 = 0.01; + var d:f32 = 0.0; + + var i: i32 = 0; + loop { + if(i >= MAX_STEPS){ + break; + } + + let p = ro + rd * d; + let ds = getDist(p); + d = d + ds; + if(d > MAX_DIST || ds <= SURF_DIST){ + break; + } + + i = i + 1; + } + return d; +} + +fn getNormal(p:vec3) -> vec3{ + let d = getDist(p); + let e = vec2(0.1,0.0); + + // We can find the normal using the points around the hit point + let n = d - vec3( + getDist(p-e.xyy), + getDist(p-e.yxy), + getDist(p-e.yyx) + ); + + return normalize(n); +} + +fn getLight(p:vec3) -> f32{ + let SURF_DIST:f32 = .01; + + let light_pos = vec3(0.,5.,0.); + let l = normalize(light_pos - p) * 1.; + let n = getNormal(p); + + var dif = clamp(dot(n,l),.0,1.); + + let d = rayMarch(p + n * SURF_DIST * 2.,l); + if(d +) -> @location(0) vec4 { + let aspect = ubo.resolution / min(ubo.resolution.x,ubo.resolution.y); + let tmp_uv = (uv - vec2(0.5,0.5)) * aspect * 2.0; + var col = vec3(0.0); + + let r_origin = vec3(4.0,3.,.0); + let r_dir = normalize(vec3(-1.0,tmp_uv.y,tmp_uv.x)); + let d = rayMarch(r_origin,r_dir); + col = vec3(d / 8.); + let p = r_origin + r_dir * d; + let diff = getLight(p); + + col = vec3(0.0, diff, 0.0); + return vec4(col,0.0); +} diff --git a/shaderexp/main.zig b/shaderexp/main.zig new file mode 100755 index 00000000..ead03f37 --- /dev/null +++ b/shaderexp/main.zig @@ -0,0 +1,329 @@ +const std = @import("std"); +const mach = @import("mach"); +const gpu = @import("gpu"); +const glfw = @import("glfw"); + +const App = mach.App(*FrameParams, .{}); + +const Vertex = struct { + pos: @Vector(4, f32), + uv: @Vector(2, f32), +}; + +const vertices = [_]Vertex{ + .{ .pos = .{ -1, -1, 0, 1 }, .uv = .{ 0, 0 } }, + .{ .pos = .{ 1, -1, 0, 1 }, .uv = .{ 1, 0 } }, + .{ .pos = .{ 1, 1, 0, 1 }, .uv = .{ 1, 1 } }, + .{ .pos = .{ -1, 1, 0, 1 }, .uv = .{ 0, 1 } }, +}; +const indices = [_]u16{ 0, 1, 2, 2, 3, 0 }; + +const UniformBufferObject = struct { + resolution: @Vector(2, f32), + time: f32, +}; + +var timer: std.time.Timer = undefined; + +pub fn main() !void { + timer = try std.time.Timer.start(); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var allocator = gpa.allocator(); + + const ctx = try allocator.create(FrameParams); + var app = try App.init(allocator, ctx, .{}); + + app.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); + // On linux if we don't set a minimum size, you can squish the window to 0 pixels of width and height, + // this makes some strange effects when that happens, so it's better to leave a minimum size to avoid that, + // this doesn't prevent you from minimizing the window. + try app.window.setSizeLimits(.{ .width = 20, .height = 20 }, .{ .width = null, .height = null }); + + var fragment_file: std.fs.File = undefined; + var last_mtime: i128 = undefined; + if (std.fs.cwd().openFile("shaderexp/frag.wgsl", .{ .mode = .read_only })) |file| { + fragment_file = file; + if (file.stat()) |stat| { + last_mtime = stat.mtime; + } else |err| { + std.debug.print("Something went wrong when attempting to stat file: {}\n", .{err}); + return; + } + } else |e| { + std.debug.print("Something went wrong when attempting to open file: {}\n", .{e}); + return; + } + defer fragment_file.close(); + var code = try fragment_file.readToEndAllocOptions(allocator, std.math.maxInt(u16), null, 1, 0); + defer allocator.free(code); + + const queue = app.device.getQueue(); + + const vertex_buffer = app.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(); + defer vertex_buffer.release(); + + const index_buffer = app.device.createBuffer(&.{ + .usage = .{ .index = true }, + .size = @sizeOf(u16) * indices.len, + .mapped_at_creation = true, + }); + var index_mapped = index_buffer.getMappedRange(@TypeOf(indices[0]), 0, indices.len); + std.mem.copy(u16, index_mapped, indices[0..]); + index_buffer.unmap(); + defer index_buffer.release(); + + // We need a bgl to bind the UniformBufferObject, but it is also needed for creating + // the RenderPipeline, so we pass it to recreatePipeline as a pointer + var bgl: gpu.BindGroupLayout = undefined; + const pipeline = recreatePipeline(&app, code, &bgl); + + const uniform_buffer = app.device.createBuffer(&.{ + .usage = .{ .copy_dst = true, .uniform = true }, + .size = @sizeOf(UniformBufferObject), + .mapped_at_creation = false, + }); + defer uniform_buffer.release(); + const bind_group = app.device.createBindGroup( + &gpu.BindGroup.Descriptor{ + .layout = bgl, + .entries = &.{ + gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), + }, + }, + ); + defer bind_group.release(); + + ctx.* = FrameParams{ + .pipeline = pipeline, + .queue = queue, + .vertex_buffer = vertex_buffer, + .index_buffer = index_buffer, + .uniform_buffer = uniform_buffer, + .bind_group = bind_group, + + .fragment_shader_file = fragment_file, + .fragment_shader_code = code, + .last_mtime = last_mtime, + }; + + bgl.release(); + + try app.run(.{ .frame = frame }); +} + +const FrameParams = struct { + pipeline: gpu.RenderPipeline, + queue: gpu.Queue, + vertex_buffer: gpu.Buffer, + index_buffer: gpu.Buffer, + uniform_buffer: gpu.Buffer, + bind_group: gpu.BindGroup, + + fragment_shader_file: std.fs.File, + fragment_shader_code: [:0]const u8, + last_mtime: i128, +}; + +fn frame(app: *App, params: *FrameParams) !void { + if (params.fragment_shader_file.stat()) |stat| { + if (params.last_mtime < stat.mtime) { + std.log.info("The fragment shader has been changed", .{}); + params.last_mtime = stat.mtime; + params.fragment_shader_file.seekTo(0) catch unreachable; + params.fragment_shader_code = params.fragment_shader_file.readToEndAllocOptions(app.allocator, std.math.maxInt(u32), null, 1, 0) catch |err| { + std.log.err("Err: {}", .{err}); + return; + }; + params.pipeline = recreatePipeline(app, params.fragment_shader_code, null); + } + } else |err| { + std.log.err("Something went wrong when attempting to stat file: {}\n", .{err}); + } + + const back_buffer_view = app.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 = app.device.createCommandEncoder(null); + const render_pass_info = gpu.RenderPassEncoder.Descriptor{ + .color_attachments = &.{color_attachment}, + .depth_stencil_attachment = null, + }; + + const time = @intToFloat(f32, timer.read()) / @as(f32, std.time.ns_per_s); + const ubo = UniformBufferObject{ + .resolution = .{ @intToFloat(f32, app.current_desc.width), @intToFloat(f32, app.current_desc.height) }, + .time = time, + }; + encoder.writeBuffer(params.uniform_buffer, 0, UniformBufferObject, &.{ubo}); + + const pass = encoder.beginRenderPass(&render_pass_info); + pass.setVertexBuffer(0, params.vertex_buffer, 0, @sizeOf(Vertex) * vertices.len); + pass.setIndexBuffer(params.index_buffer, .uint16, 0, @sizeOf(u16) * indices.len); + pass.setPipeline(params.pipeline); + pass.setBindGroup(0, params.bind_group, &.{0}); + pass.drawIndexed(indices.len, 1, 0, 0, 0); + pass.end(); + pass.release(); + + var command = encoder.finish(null); + encoder.release(); + + params.queue.submit(&.{command}); + command.release(); + app.swap_chain.?.present(); + back_buffer_view.release(); +} + +fn recreatePipeline(app: *const App, fragment_shader_code: [:0]const u8, bgl: ?*gpu.BindGroupLayout) gpu.RenderPipeline { + const vs_module = app.device.createShaderModule(&.{ + .label = "my vertex shader", + .code = .{ .wgsl = @embedFile("vert.wgsl") }, + }); + defer vs_module.release(); + 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, + }; + + // Check wether the fragment shader code compiled successfully, if not + // print the validation layer error and show a black screen + app.device.pushErrorScope(.validation); + var fs_module = app.device.createShaderModule(&gpu.ShaderModule.Descriptor{ + .label = "my fragment shader", + .code = .{ .wgsl = fragment_shader_code }, + }); + var error_occurred: bool = false; + // popErrorScope() returns always true, (unless maybe it fails to capture the error scope?) + _ = app.device.popErrorScope(&gpu.ErrorCallback.init(*bool, &error_occurred, struct { + fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void { + if (typ != .noError) { + std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message}); + ctx.* = true; + } + } + }.callback)); + if (error_occurred) { + fs_module = app.device.createShaderModule(&gpu.ShaderModule.Descriptor{ + .label = "my fragment shader", + .code = .{ .wgsl = @embedFile("black_screen_frag.wgsl") }, + }); + } + defer fs_module.release(); + + const blend = gpu.BlendState{ + .color = .{ + .operation = .add, + .src_factor = .one, + .dst_factor = .zero, + }, + .alpha = .{ + .operation = .add, + .src_factor = .one, + .dst_factor = .zero, + }, + }; + const color_target = gpu.ColorTargetState{ + .format = app.swap_chain_format, + .blend = &blend, + .write_mask = gpu.ColorWriteMask.all, + }; + const fragment = gpu.FragmentState{ + .module = fs_module, + .entry_point = "main", + .targets = &.{color_target}, + .constants = null, + }; + + const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .fragment = true }, .uniform, true, 0); + // bgl is needed outside, for the creation of the uniform_buffer in main + const bgl_tmp = app.device.createBindGroupLayout( + &gpu.BindGroupLayout.Descriptor{ + .entries = &.{bgle}, + }, + ); + defer { + // In frame we don't need to use bgl, so we can release it inside this function, else we pass bgl + if (bgl == null) { + bgl_tmp.release(); + } else { + bgl.?.* = bgl_tmp; + } + } + + const bind_group_layouts = [_]gpu.BindGroupLayout{bgl_tmp}; + const pipeline_layout = app.device.createPipelineLayout(&.{ + .bind_group_layouts = &bind_group_layouts, + }); + defer pipeline_layout.release(); + + 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, + }, + }; + + // Create the render pipeline. Even if the shader compilation succeeded, this could fail if the + // shader is missing a `main` entrypoint. + app.device.pushErrorScope(.validation); + const pipeline = app.device.createRenderPipeline(&pipeline_descriptor); + // popErrorScope() returns always true, (unless maybe it fails to capture the error scope?) + _ = app.device.popErrorScope(&gpu.ErrorCallback.init(*bool, &error_occurred, struct { + fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void { + if (typ != .noError) { + std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message}); + ctx.* = true; + } + } + }.callback)); + if (error_occurred) { + // Retry with black_screen_frag which we know will work. + return recreatePipeline(app, @embedFile("black_screen_frag.wgsl"), bgl); + } + return pipeline; +} diff --git a/shaderexp/vert.wgsl b/shaderexp/vert.wgsl new file mode 100755 index 00000000..dbf0efbd --- /dev/null +++ b/shaderexp/vert.wgsl @@ -0,0 +1,14 @@ +struct VertexOut { + @builtin(position) position_clip : vec4; + @location(0) frag_uv : vec2; +} + +@stage(vertex) fn main( + @location(0) position : vec4, + @location(1) uv : vec2 +) -> VertexOut { + var output : VertexOut; + output.position_clip = position; + output.frag_uv = uv; + return output; +}