[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,