StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | struct Uniforms { |
| 2 | viewport_size: vec2<f32>, |
| 3 | // Padding necessary to ensure that the uniforms is 16 bytes. Some wgpu-supported devices (such as webgl) require |
| 4 | // buffer bindings to be a multiple of 16 bytes. |
| 5 | padding: vec2<f32> |
| 6 | } |
| 7 | |
| 8 | @group(0) @binding(0) var<uniform> uniforms: Uniforms; |
| 9 | |
| 10 | @group(1) @binding(0) var imageTexture: texture_2d<f32>; |
| 11 | @group(1) @binding(1) var imageSampler: sampler; |
| 12 | |
| 13 | struct ImageVertexShaderInput { |
| 14 | // The position of the vertex in normalized device coordinates. |
| 15 | @location(0) vertex_position: vec2<f32>, |
| 16 | @location(1) bounds: vec4<f32>, |
| 17 | @location(2) color: vec4<f32>, |
| 18 | // This field is treated as a boolean to indicate how to interpret the preceding `color` field. |
| 19 | // Icons allow overriding their foreground color, so for icons the whole `color` struct is used. |
| 20 | // For images, only the opacity can be set, and so only the alpha channel would be used. |
| 21 | @location(3) is_icon: u32, |
| 22 | // Corner radius in the order top_left, top_right, bottom_left, bottom_right. |
| 23 | @location(4) corner_radius: vec4<f32>, |
| 24 | } |
| 25 | |
| 26 | struct ImageVertexShaderOutput { |
| 27 | @builtin(position) position: vec4<f32>, |
| 28 | @location(0) rect_center: vec2<f32>, |
| 29 | @location(1) rect_corner: vec2<f32>, |
| 30 | @location(2) texture_coordinate: vec2<f32>, |
| 31 | @location(4) color: vec4<f32>, |
| 32 | @location(5) is_icon: u32, |
| 33 | @location(6) corner_radius: vec4<f32>, |
| 34 | } |
| 35 | |
| 36 | @vertex |
| 37 | fn vs_main( |
| 38 | image: ImageVertexShaderInput, |
| 39 | ) -> ImageVertexShaderOutput { |
| 40 | var out: ImageVertexShaderOutput; |
| 41 | var origin: vec2<f32> = image.bounds.xy; |
| 42 | var size: vec2<f32> = image.bounds.zw; |
| 43 | var pixel_pos: vec2<f32> = image.vertex_position * size + origin; |
| 44 | |
| 45 | // Convert the position of the item from screen coordinates into normalized device coordinates |
| 46 | var device_pos: vec2<f32> = pixel_pos / uniforms.viewport_size * vec2(2.0, -2.0) + vec2(-1.0, 1.0); |
| 47 | out.position = vec4<f32>(device_pos, 0.0, 1.0); |
| 48 | |
| 49 | // Re-compute size and origin such that they are clipped by the viewport bounds. |
| 50 | var clipped_origin = max(origin, vec2f(0.0, 0.0)); |
| 51 | var clipped_size = max(min(origin + size, uniforms.viewport_size) - clipped_origin, vec2f(0.0, 0.0)); |
| 52 | out.rect_corner = clipped_size / 2.0; |
| 53 | out.rect_center = clipped_origin + out.rect_corner; |
| 54 | |
| 55 | out.texture_coordinate = image.vertex_position; |
| 56 | out.color = image.color; |
| 57 | out.is_icon = image.is_icon; |
| 58 | out.corner_radius = image.corner_radius; |
| 59 | return out; |
| 60 | } |
| 61 | |
| 62 | fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, corner_radius: f32) -> f32 { |
| 63 | var p: vec2<f32> = pixel_pos - rect_center; |
| 64 | var q: vec2<f32> = abs(p) - rect_corner + corner_radius; |
| 65 | return length(max(q, vec2(0.0))) + min(max(q.x, q.y), 0.0) - corner_radius; |
| 66 | } |
| 67 | |
| 68 | @fragment |
| 69 | fn fs_main(in: ImageVertexShaderOutput) -> @location(0) vec4<f32> { |
| 70 | // Sample the texture to obtain a color. |
| 71 | var color_sample: vec4<f32> = textureSample(imageTexture, imageSampler, in.texture_coordinate); |
| 72 | |
| 73 | var color: vec4<f32>; |
| 74 | if in.is_icon == 0u { |
| 75 | // For an image, use the image color and just adjust opacity. |
| 76 | color = color_sample; |
| 77 | color.a *= in.color.a; |
| 78 | } else { |
| 79 | // There's a naga bug with wgsl --> hlsl conversion where images are always rendered as red. |
| 80 | // We workaround this by first creating an intermediate color where the alpha channel is actually the |
| 81 | // red channel from `color_sample` and then multiplying that by the desired opacity. |
| 82 | var new_color: vec4<f32> = vec4(color_sample.r, color_sample.g, color_sample.b, color_sample.r); |
| 83 | new_color.a *= in.color.a; |
| 84 | // For an icon, use the specified input color. |
| 85 | color = vec4(in.color.r, in.color.g, in.color.b, new_color.a); |
| 86 | } |
| 87 | |
| 88 | var outer_corner_radius: f32; |
| 89 | |
| 90 | if in.position.y >= in.rect_center.y { |
| 91 | // Bottom half |
| 92 | if in.position.x >= in.rect_center.x { |
| 93 | // Bottom right quadrant |
| 94 | outer_corner_radius = in.corner_radius.w; |
| 95 | } else { |
| 96 | // Bottom left quadrant |
| 97 | outer_corner_radius = in.corner_radius.z; |
| 98 | } |
| 99 | } else { |
| 100 | // Top half |
| 101 | if in.position.x >= in.rect_center.x { |
| 102 | // Top right quadrant |
| 103 | outer_corner_radius = in.corner_radius.y; |
| 104 | } else { |
| 105 | // Top left quadrant |
| 106 | outer_corner_radius = in.corner_radius.x; |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | var outer_distance: f32 = distance_from_rect(in.position.xy, in.rect_center, in.rect_corner, outer_corner_radius); |
| 111 | |
| 112 | // If there's a corner radius we need to do some anti aliasing to smooth out the rounded corner effect. |
| 113 | if outer_corner_radius > 0. { |
| 114 | color.a *= 1.0 - saturate(outer_distance + 0.5); |
| 115 | } |
| 116 | |
| 117 | return color; |
| 118 | } |
| 119 |