struct Settings { eraserRadiusSquared: f32, lineDistanceEpsilon: f32, clearRed: f32, clearGreen: f32, clearBlue: f32, clearAlpha: f32, eraserRadius: f32, }; @group(1) @binding(0) var settings: Settings; struct VertexOutput { @builtin(position) position: vec4, @location(0) screenPosition: vec2, @location(1) @interpolate(flat) start: vec2, @location(2) @interpolate(flat) direction: vec2, @location(3) @interpolate(flat) inverseLengthSquared: f32, } struct EraserCombinedTargets { @location(0) mask: vec4, @location(1) source: vec4, @location(2) trail: vec4, } @vertex fn vertex( @builtin(vertex_index) vertexIndex: u32, @location(0) start: vec2, @location(1) end: vec2 ) -> VertexOutput { let direction = end - start; let denominator = dot(direction, direction); var inverseLengthSquared = 0.0; if denominator > settings.lineDistanceEpsilon { inverseLengthSquared = 1.0 / denominator; } let screenPosition = segment_vertex_position(vertexIndex, start, end, settings.eraserRadius); let uv = screenPosition / state.size; let position = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0); return VertexOutput(vec4(position, 0.0, 1.0), screenPosition, start, direction, inverseLengthSquared); } @fragment fn fragmentCombined( @location(0) screenPosition: vec2, @location(1) @interpolate(flat) start: vec2, @location(2) @interpolate(flat) direction: vec2, @location(3) @interpolate(flat) inverseLengthSquared: f32 ) -> EraserCombinedTargets { let distanceSquared = distance_squared_from_segment( screenPosition, start, direction, inverseLengthSquared ); if distanceSquared > settings.eraserRadiusSquared { discard; } let cleared = getEraserClearValue(); return EraserCombinedTargets(getEraserMaskValue(), cleared, cleared); } fn getEraserMaskValue() -> vec4 { return vec4(settings.clearAlpha, 0.0, 0.0, 1.0); } fn getEraserClearValue() -> vec4 { return vec4( settings.clearRed, settings.clearGreen, settings.clearBlue, settings.clearAlpha ); }