Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/rendering/wgpu/shaders/image_shader.wgsl
StratoSDK / crates / strato-ui-renderer / src / rendering / wgpu / shaders / image_shader.wgsl
1struct 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 
13struct 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 
26struct 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
37fn 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 
62fn 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
69fn 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