[forma] Implement GPU gradient.

Make each style in the buffer variable in size.

Change-Id: I9af48582eea40cbbb63fe60eecef779ed9551fa2
Reviewed-on: https://fuchsia-review.googlesource.com/c/forma/+/689484
Reviewed-by: DragoČ™ Tiselice <dtiselice@google.com>
diff --git a/gpu/painter/src/lib.rs b/gpu/painter/src/lib.rs
index 89221a3..957fc18 100644
--- a/gpu/painter/src/lib.rs
+++ b/gpu/painter/src/lib.rs
@@ -56,7 +56,7 @@
 
 pub fn init(device: &wgpu::Device) -> PaintContext {
     let module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
-        label: None,
+        label: Some("paint.wgsl"),
         source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("paint.wgsl"))),
     });
 
@@ -79,7 +79,7 @@
     texture_view: &wgpu::TextureView,
     segments_buffer: &wgpu::Buffer,
     segments_len: u32,
-    style_indices_buffer: &wgpu::Buffer,
+    style_offsets_buffer: &wgpu::Buffer,
     styles_buffer: &wgpu::Buffer,
     width: u32,
     height: u32,
@@ -100,7 +100,7 @@
         entries: &[
             wgpu::BindGroupEntry { binding: 0, resource: config_buffer.as_entire_binding() },
             wgpu::BindGroupEntry { binding: 1, resource: segments_buffer.as_entire_binding() },
-            wgpu::BindGroupEntry { binding: 2, resource: style_indices_buffer.as_entire_binding() },
+            wgpu::BindGroupEntry { binding: 2, resource: style_offsets_buffer.as_entire_binding() },
             wgpu::BindGroupEntry { binding: 3, resource: styles_buffer.as_entire_binding() },
             wgpu::BindGroupEntry {
                 binding: 4,
diff --git a/gpu/painter/src/paint.wgsl b/gpu/painter/src/paint.wgsl
index 67737bb..07fad37 100644
--- a/gpu/painter/src/paint.wgsl
+++ b/gpu/painter/src/paint.wgsl
@@ -186,13 +186,94 @@
 @group(0) @binding(0) var<uniform> config: Config;
 @group(0) @binding(1) var<storage> segments: array<PixelSegment>;
 @group(0) @binding(2) var<storage> style_indices: array<u32>;
-@group(0) @binding(3) var<storage> styles: array<Style>;
+@group(0) @binding(3) var<storage> styles: array<u32>;
 @group(0) @binding(4) var image: texture_storage_2d<rgba16float, write>;
 
 var<workgroup> segment_block: array<OptimizedSegment, BLOCK_LEN>;
 var<private> segment_index: u32;
 var<private> block_index: u32;
 
+// Returns how many colors and stops the gradient has.
+// Returns 0 when the fill type is not a gradient.
+fn getGradientStopsCount(style_header: u32) -> u32{
+    let STYLE_STOPS_COUNT_BITS = 16u;
+    let STYLE_STOPS_COUNT_OFFSET = 0u;
+    return extractBits(style_header, STYLE_STOPS_COUNT_OFFSET, STYLE_STOPS_COUNT_BITS);
+}
+
+// Returns `paint::BlendMode` ordinal.
+fn getBlendMode(style_header:u32) -> u32 {
+    let STYLE_BLEND_MODE_BITS = 4u;
+    let STYLE_BLEND_MODE_OFFSET = 16u; // STYLE_STOPS_COUNT_BITS + STYLE_STOPS_COUNT_OFFSET.
+    return extractBits(style_header, STYLE_BLEND_MODE_OFFSET, STYLE_BLEND_MODE_BITS);
+}
+
+// Returns the fill function by position in the following list:
+// [Solid, Linear gradient, Radial gradient, Texture]
+fn getFillType(style_header: u32) -> u32 {
+    let STYLE_FILL_BITS = 2u;
+    let STYLE_FILL_OFFSET = 20u; // STYLE_BLEND_MODE_BITS + STYLE_BLEND_MODE_OFFSET.
+    return extractBits(style_header, STYLE_FILL_OFFSET, STYLE_FILL_BITS);
+}
+
+// Returns 1 for `FillRule::EvenOdd` and 0 for `FillRile::NonZero`.
+fn getFillRule(style_header: u32) -> u32 {
+    let STYLE_FILL_RULE_BITS = 1u;
+    let STYLE_FILL_RULE_OFFSET = 22u; // STYLE_FILL_BITS + STYLE_FILL_OFFSET.
+    return extractBits(style_header, STYLE_FILL_RULE_OFFSET, STYLE_FILL_RULE_BITS);
+}
+
+// Retuns `Style::is_clipped` value.
+fn getIsClipped(style_header: u32) -> u32 {
+    let IS_CLIPPED_BITS = 1u;
+    let IS_CLIPPED_OFFSET = 23u; // STYLE_FILL_RULE_BITS + STYLE_FILL_RULE_BITS.
+    return extractBits(style_header, IS_CLIPPED_OFFSET, IS_CLIPPED_BITS);
+}
+
+// Returns 0 for `Func::Draw` and 1 for `Func::Clip`.
+fn getFunc(style_header: u32) -> u32 {
+    let FUNC_BITS = 1u;
+    let FUNC_OFFSET = 24u;
+    return extractBits(style_header, FUNC_OFFSET, FUNC_BITS);
+}
+
+// Reads a vector from the style buffer at the given offset.
+fn getVec4F32(offset:u32) -> vec4<f32> {
+    return vec4(
+        bitcast<f32>(styles[offset]),
+        bitcast<f32>(styles[offset + 1u]),
+        bitcast<f32>(styles[offset + 2u]),
+        bitcast<f32>(styles[offset + 3u]),
+    );
+}
+
+// Returns the color used by solid fill function.
+fn getSolidColor(offset: u32) -> vec4<f32> {
+    return getVec4F32(offset + 1u);
+}
+
+// Returns the two 2D points for the gradient packed into a vector.
+fn getGradientStartEnd(offset: u32) -> vec4<f32> {
+    return getVec4F32(offset + 1u);
+}
+
+// Returns the color of the Nth gradient stop.
+fn getGradientColor(offset: u32, stop_idx: u32) -> vec4<f32> {
+    let SKIP_HEADER = 1u;
+    let SKIP_START_END = 4u;
+    let offset = offset + SKIP_HEADER + SKIP_START_END + stop_idx * 5u;
+    return getVec4F32(offset);
+}
+
+// Returns the value the Nth gradient stop.
+fn getGradientStop(offset: u32, stop_idx: u32) -> f32 {
+    let SKIP_HEADER = 1u;
+    let SKIP_START_END = 4u;
+    let SKIP_COLOR = 4u;
+    let offset = offset + SKIP_HEADER + SKIP_START_END + stop_idx * 5u + SKIP_COLOR;
+    return bitcast<f32>(styles[offset]);
+}
+
 fn loadSegments(tile_y: i32, local_index: u32) -> bool {
     if block_index > (config.segments_len >> BLOCK_SHIFT) {
         return false;
@@ -475,11 +556,11 @@
 fn painterPushCover(
     painter: ptr<function, Painter>,
     layer_id: u32,
-    fill_rule: u32,
+    style_header: u32,
     local_id: vec2<u32>,
 ) {
     var mask: u32;
-    switch fill_rule {
+    switch getFillRule(style_header) {
         // NonZero
         case 0u {
             mask = 4294967295u;
@@ -518,27 +599,74 @@
 fn painterBlendLayer(
     painter: ptr<function, Painter>,
     layer_id: u32,
+    pixel_coords: vec2<u32>,
     local_id: vec2<u32>,
 ) {
-    let style = styles[style_indices[layer_id]];
+    let style_offset = style_indices[layer_id];
+    let style_header = styles[style_offset];
+    painterPushCover(painter, layer_id, style_header, local_id);
 
-    painterPushCover(painter, layer_id, style.fill_rule, local_id);
+    var src: vec4<f32>;
 
-    let src = vec4(
-        style.color.r,
-        style.color.g,
-        style.color.b,
-        style.color.a * areaToCoverage((*painter).double_area, style.fill_rule),
-    );
+    // Select the default branch when `getFunc(style_header)` is 1 which
+    // means the function is `Func::Clip`.
+    let fill_type = getFillType(style_header) + getFunc(style_header) * 4u;
+    switch fill_type {
+        // Solid color.
+        case 0 {
+            src = getSolidColor(style_offset);
+        }
+
+        // Gradients.
+        case 1u, 2u {
+            let start_end = getGradientStartEnd(style_offset);
+            let start = start_end.xy;
+            let end = start_end.zw;
+            let d = end - start;
+            let p = vec2<f32>(pixel_coords) - start;
+            var t: f32;
+            switch fill_type {
+                // Linear gradient.
+                case 1u: {
+                    t = clamp(dot(p, d) / dot(d, d), 0.0, 1.0);
+                }
+                // Radial gradient.
+                default {
+                    t = sqrt(dot(p, p) / dot(d, d));
+                }
+            }
+            var i: u32 = getGradientStopsCount(style_header) - 1u;
+            loop {
+                if i <= 0u | getGradientStop(style_offset, i) < t { break; }
+                i--;
+            }
+            let from_color = getGradientColor(style_offset, i);
+            let from_stop = getGradientStop(style_offset, i);
+            let to_color = getGradientColor(style_offset, i + 1u);
+            let to_stop = getGradientStop(style_offset, i + 1u);
+            let t = (t - from_stop) / (to_stop - from_stop);
+            src = mix(from_color, to_color, t);
+        }
+        // Texture.
+        case 3u {
+            src = vec4(0.0, 0.0, 0.0, 0.0);
+        }
+        // Clipping.
+        default {
+            src = vec4(0.0, 0.0, 0.0, 0.0);
+        }
+    }
+    src.a *= areaToCoverage((*painter).double_area, getFillRule(style_header));
 
     (*painter).double_area = 0;
     (*painter).cover = 0;
-    (*painter).color = blend((*painter).color, src, style.blend_mode);
+    (*painter).color = blend((*painter).color, src, getBlendMode(style_header));
 }
 
 fn painterPopQueueUntil(
     painter: ptr<function, Painter>,
     layer_id: u32,
+    pixel_coords: vec2<u32>,
     local_id: vec2<u32>,
 ) {
     while (*painter).queues.start0 != (*painter).queues.end0 {
@@ -554,7 +682,7 @@
         (*painter).cover += cover;
 
         if current_layer_id < layer_id {
-            painterBlendLayer(painter, current_layer_id, local_id);
+            painterBlendLayer(painter, current_layer_id, pixel_coords, local_id);
         }
 
         (*painter).queues.start0 = ((*painter).queues.start0 + 1u) &
@@ -585,11 +713,11 @@
 
             if current_layer_id != layer_id {
                 if layer_id != LAYER_ID_NONE {
-                    let style = styles[style_indices[layer_id]];
+                    let style_header = styles[style_indices[layer_id]];
                     painterPushCover(
                         painter,
                         layer_id,
-                        style.fill_rule,
+                        style_header,
                         local_id,
                     );
                     (*painter).cover = 0;
@@ -614,8 +742,8 @@
 
         if should_break {
             if layer_id != LAYER_ID_NONE {
-                let style = styles[style_indices[layer_id]];
-                painterPushCover(painter, layer_id, style.fill_rule, local_id);
+                let style_header = styles[style_indices[layer_id]];
+                painterPushCover(painter, layer_id, style_header, local_id);
                 (*painter).cover = 0;
             }
 
@@ -628,6 +756,7 @@
     painter: ptr<function, Painter>,
     tile: vec2<i32>,
     local_index: u32,
+    pixel_coords: vec2<u32>,
     local_id: vec2<u32>,
 ) {
     var seg: OptimizedSegment;
@@ -647,10 +776,10 @@
 
             if current_layer_id != layer_id {
                 if layer_id != LAYER_ID_NONE {
-                    painterBlendLayer(painter, layer_id, local_id);
+                    painterBlendLayer(painter, layer_id, pixel_coords, local_id);
                 }
 
-                painterPopQueueUntil(painter, current_layer_id, local_id);
+                painterPopQueueUntil(painter, current_layer_id, pixel_coords, local_id);
 
                 layer_id = current_layer_id;
             }
@@ -685,10 +814,10 @@
 
         if should_break {
             if layer_id != LAYER_ID_NONE {
-                painterBlendLayer(painter, layer_id, local_id);
+                painterBlendLayer(painter, layer_id, pixel_coords, local_id);
             }
 
-            painterPopQueueUntil(painter, LAYER_ID_NONE, local_id);
+            painterPopQueueUntil(painter, LAYER_ID_NONE, pixel_coords, local_id);
 
             break;
         }
@@ -745,12 +874,12 @@
 
     while u32(tile.x) <= tile_row_len {
         painter.color = clearColor();
-        painterPaintTile(&painter, tile, local_index, local_id);
-
-        textureStore(image, vec2<i32>(local_id) + tile * vec2(
+        let pixel_coords = vec2<i32>(local_id) + tile * vec2(
             i32(TILE_WIDTH),
             i32(TILE_HEIGHT),
-        ), painter.color);
+        );
+        painterPaintTile(&painter, tile, local_index, vec2<u32>(pixel_coords), local_id);
+        textureStore(image, pixel_coords, painter.color);
 
         painter.queues.end0 = painter.queues.start1;
 
diff --git a/gpu/renderer/src/lib.rs b/gpu/renderer/src/lib.rs
index fc91bd7..eab569b 100644
--- a/gpu/renderer/src/lib.rs
+++ b/gpu/renderer/src/lib.rs
@@ -112,8 +112,8 @@
         width: u32,
         height: u32,
         segments: &[u64],
-        style_indices: &[u32],
-        styles: &[Style],
+        style_offsets: &[u32],
+        styles: &[u32],
         background_color: Color,
     ) -> Result<Option<Timings>, Error> {
         let frame = surface.get_current_texture()?;
@@ -213,10 +213,10 @@
                 &offsets_buffer,
                 timestamp_context.as_ref().map(|(timestamp, _, _)| (timestamp, 0, 1)),
             );
-            let style_indicess_buffer =
+            let style_offsets_buffer =
                 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                     label: None,
-                    contents: bytemuck::cast_slice(style_indices),
+                    contents: bytemuck::cast_slice(style_offsets),
                     usage: wgpu::BufferUsages::STORAGE,
                 });
             let styles_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
@@ -232,7 +232,7 @@
                 &texture_view,
                 segments_buffer,
                 old_len as u32,
-                &style_indicess_buffer,
+                &style_offsets_buffer,
                 &styles_buffer,
                 width,
                 height,