From 6551a0b2ab6c831dd1d3646ecf55180339275e22 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 11 May 2023 09:12:06 -0700 Subject: Added support for gradients as background variants + other optimizations. --- wgpu/src/shader/gradient.wgsl | 88 ------------- wgpu/src/shader/quad.wgsl | 298 +++++++++++++++++++++++++++++++++++------- wgpu/src/shader/solid.wgsl | 30 ----- wgpu/src/shader/triangle.wgsl | 168 ++++++++++++++++++++++++ 4 files changed, 422 insertions(+), 162 deletions(-) delete mode 100644 wgpu/src/shader/gradient.wgsl delete mode 100644 wgpu/src/shader/solid.wgsl create mode 100644 wgpu/src/shader/triangle.wgsl (limited to 'wgpu/src/shader') diff --git a/wgpu/src/shader/gradient.wgsl b/wgpu/src/shader/gradient.wgsl deleted file mode 100644 index 63825aec..00000000 --- a/wgpu/src/shader/gradient.wgsl +++ /dev/null @@ -1,88 +0,0 @@ -struct Uniforms { - transform: mat4x4, - //xy = start, wz = end - position: vec4, - //x = start stop, y = end stop, zw = padding - stop_range: vec4, -} - -struct Stop { - color: vec4, - offset: f32, -}; - -@group(0) @binding(0) -var uniforms: Uniforms; - -@group(0) @binding(1) -var color_stops: array; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) raw_position: vec2 -} - -@vertex -fn vs_main(@location(0) input: vec2) -> VertexOutput { - var output: VertexOutput; - output.position = uniforms.transform * vec4(input.xy, 0.0, 1.0); - output.raw_position = input; - - return output; -} - -//TODO: rewrite without branching -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - let start = uniforms.position.xy; - let end = uniforms.position.zw; - let start_stop = uniforms.stop_range.x; - let end_stop = uniforms.stop_range.y; - - let v1 = end - start; - let v2 = input.raw_position.xy - start; - let unit = normalize(v1); - let offset = dot(unit, v2) / length(v1); - - let min_stop = color_stops[start_stop]; - let max_stop = color_stops[end_stop]; - - var color: vec4; - - if (offset <= min_stop.offset) { - color = min_stop.color; - } else if (offset >= max_stop.offset) { - color = max_stop.color; - } else { - var min = min_stop; - var max = max_stop; - var min_index = start_stop; - var max_index = end_stop; - - loop { - if (min_index >= max_index - 1) { - break; - } - - let index = min_index + (max_index - min_index) / 2; - - let stop = color_stops[index]; - - if (offset <= stop.offset) { - max = stop; - max_index = index; - } else { - min = stop; - min_index = index; - } - } - - color = mix(min.color, max.color, smoothstep( - min.offset, - max.offset, - offset - )); - } - - return color; -} diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index cf4f7e4d..3232bdbe 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,17 +5,50 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -struct VertexInput { +fn distance_alg( + frag_coord: vec2, + position: vec2, + size: vec2, + radius: f32 +) -> f32 { + var inner_size: vec2 = size - vec2(radius, radius) * 2.0; + var top_left: vec2 = position + vec2(radius, radius); + var bottom_right: vec2 = top_left + inner_size; + + var top_left_distance: vec2 = top_left - frag_coord; + var bottom_right_distance: vec2 = frag_coord - bottom_right; + + var dist: vec2 = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(dist.x * dist.x + dist.y * dist.y); +} + +// Based on the fragement position and the center of the quad, select one of the 4 radi. +// Order matches CSS border radius attribute: +// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left +fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { + var rx = radi.x; + var ry = radi.y; + rx = select(radi.x, radi.y, position.x > center.x); + ry = select(radi.w, radi.z, position.x > center.x); + rx = select(rx, ry, position.y > center.y); + return rx; +} + +struct SolidVertexInput { @location(0) v_pos: vec2, - @location(1) pos: vec2, - @location(2) scale: vec2, - @location(3) color: vec4, + @location(1) color: vec4, + @location(2) pos: vec2, + @location(3) scale: vec2, @location(4) border_color: vec4, @location(5) border_radius: vec4, @location(6) border_width: f32, } -struct VertexOutput { +struct SolidVertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, @location(1) border_color: vec4, @@ -26,8 +59,8 @@ struct VertexOutput { } @vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; var pos: vec2 = input.pos * globals.scale; var scale: vec2 = input.scale * globals.scale; @@ -47,54 +80,20 @@ fn vs_main(input: VertexInput) -> VertexOutput { vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) ); + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); out.color = input.color; out.border_color = input.border_color; out.pos = pos; out.scale = scale; out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; - out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); return out; } -fn distance_alg( - frag_coord: vec2, - position: vec2, - size: vec2, - radius: f32 -) -> f32 { - var inner_size: vec2 = size - vec2(radius, radius) * 2.0; - var top_left: vec2 = position + vec2(radius, radius); - var bottom_right: vec2 = top_left + inner_size; - - var top_left_distance: vec2 = top_left - frag_coord; - var bottom_right_distance: vec2 = frag_coord - bottom_right; - - var dist: vec2 = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(dist.x * dist.x + dist.y * dist.y); -} - -// Based on the fragement position and the center of the quad, select one of the 4 radi. -// Order matches CSS border radius attribute: -// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left -fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { - var rx = radi.x; - var ry = radi.y; - rx = select(radi.x, radi.y, position.x > center.x); - ry = select(radi.w, radi.z, position.x > center.x); - rx = select(rx, ry, position.y > center.y); - return rx; -} - - @fragment -fn fs_main( - input: VertexOutput +fn solid_fs_main( + input: SolidVertexOutput ) -> @location(0) vec4 { var mixed_color: vec4 = input.color; @@ -138,3 +137,214 @@ fn fs_main( return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); } + +struct GradientVertexInput { + @location(0) v_pos: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, + @location(12) position_and_scale: vec4, + @location(13) border_color: vec4, + @location(14) border_radius: vec4, + @location(15) border_width: f32 +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, + @location(12) position_and_scale: vec4, + @location(13) border_color: vec4, + @location(14) border_radius: vec4, + @location(15) border_width: f32 +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var out: GradientVertexOutput; + + var pos: vec2 = input.position_and_scale.xy * globals.scale; + var scale: vec2 = input.position_and_scale.zw * globals.scale; + + var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; + var border_radius: vec4 = vec4( + min(input.border_radius.x, min_border_radius), + min(input.border_radius.y, min_border_radius), + min(input.border_radius.z, min_border_radius), + min(input.border_radius.w, min_border_radius) + ); + + var transform: mat4x4 = mat4x4( + vec4(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + out.color_1 = input.color_1; + out.color_2 = input.color_2; + out.color_3 = input.color_3; + out.color_4 = input.color_4; + out.color_5 = input.color_5; + out.color_6 = input.color_6; + out.color_7 = input.color_7; + out.color_8 = input.color_8; + out.offsets_1 = input.offsets_1; + out.offsets_2 = input.offsets_2; + out.direction = input.direction * globals.scale; + out.position_and_scale = vec4(pos, scale); + out.border_color = input.border_color; + out.border_radius = border_radius * globals.scale; + out.border_width = input.border_width * globals.scale; + + return out; +} + +fn random(coords: vec2) -> f32 { + return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + let noise_granularity: f32 = 0.3/255.0; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + color = mix(colors_arr[i], colors_arr[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset, + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + input.color_1, + input.color_2, + input.color_3, + input.color_4, + input.color_5, + input.color_6, + input.color_7, + input.color_8, + ); + + var offsets = array( + input.offsets_1.x, + input.offsets_1.y, + input.offsets_1.z, + input.offsets_1.w, + input.offsets_2.x, + input.offsets_2.y, + input.offsets_2.z, + input.offsets_2.w, + ); + + //TODO could just pass this in to the shader but is probably more performant to just check it here + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] > 1.0) { + last_index = i - 1; + break; + } + } + + var mixed_color: vec4 = gradient(input.position.xy, input.direction, colors, offsets, last_index); + + let pos = input.position_and_scale.xy; + let scale = input.position_and_scale.zw; + + var border_radius = select_border_radius( + input.border_radius, + input.position.xy, + (pos + scale * 0.5).xy + ); + + if (input.border_width > 0.0) { + var internal_border: f32 = max(border_radius - input.border_width, 0.0); + + var internal_distance: f32 = distance_alg( + input.position.xy, + pos + vec2(input.border_width, input.border_width), + scale - vec2(input.border_width * 2.0, input.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(mixed_color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + } + + var dist: f32 = distance_alg( + input.position.xy, + pos, + scale, + border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothstep( + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist); + + return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); +} diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl deleted file mode 100644 index b24402f8..00000000 --- a/wgpu/src/shader/solid.wgsl +++ /dev/null @@ -1,30 +0,0 @@ -struct Globals { - transform: mat4x4, -} - -@group(0) @binding(0) var globals: Globals; - -struct VertexInput { - @location(0) position: vec2, - @location(1) color: vec4, -} - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - - out.color = input.color; - out.position = globals.transform * vec4(input.position, 0.0, 1.0); - - return out; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - return input.color; -} diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl new file mode 100644 index 00000000..625fa46e --- /dev/null +++ b/wgpu/src/shader/triangle.wgsl @@ -0,0 +1,168 @@ +struct Globals { + transform: mat4x4, +} + +@group(0) @binding(0) var globals: Globals; + +struct SolidVertexInput { + @location(0) position: vec2, + @location(1) color: vec4, +} + +struct SolidVertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} + +@vertex +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; + + out.color = input.color; + out.position = globals.transform * vec4(input.position, 0.0, 1.0); + + return out; +} + +@fragment +fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { + return input.color; +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(0) raw_position: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, +} + +@vertex +fn gradient_vs_main( + @location(0) input: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, +) -> GradientVertexOutput { + var output: GradientVertexOutput; + + output.position = globals.transform * vec4(input.xy, 0.0, 1.0); + output.raw_position = input; + output.color_1 = color_1; + output.color_2 = color_2; + output.color_3 = color_3; + output.color_4 = color_4; + output.color_5 = color_5; + output.color_6 = color_6; + output.color_7 = color_7; + output.color_8 = color_8; + output.offsets_1 = offsets_1; + output.offsets_2 = offsets_2; + output.direction = direction; + + return output; +} + +fn random(coords: vec2) -> f32 { + return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + let noise_granularity: f32 = 0.3/255.0; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + color = mix(colors_arr[i], colors_arr[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset, + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + input.color_1, + input.color_2, + input.color_3, + input.color_4, + input.color_5, + input.color_6, + input.color_7, + input.color_8, + ); + + var offsets = array( + input.offsets_1.x, + input.offsets_1.y, + input.offsets_1.z, + input.offsets_1.w, + input.offsets_2.x, + input.offsets_2.y, + input.offsets_2.z, + input.offsets_2.w, + ); + + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] >= 1.0) { + last_index = i; + break; + } + } + + return gradient(input.raw_position, input.direction, colors, offsets, last_index); +} -- cgit