| // SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense |
| |
| // Finish prefix sum of drawtags, decode draw objects. |
| |
| #import config |
| #import clip |
| #import drawtag |
| #import bbox |
| #import transform |
| |
| @group(0) @binding(0) |
| var<uniform> config: Config; |
| |
| @group(0) @binding(1) |
| var<storage> scene: array<u32>; |
| |
| @group(0) @binding(2) |
| var<storage> reduced: array<DrawMonoid>; |
| |
| @group(0) @binding(3) |
| var<storage> path_bbox: array<PathBbox>; |
| |
| @group(0) @binding(4) |
| var<storage, read_write> draw_monoid: array<DrawMonoid>; |
| |
| @group(0) @binding(5) |
| var<storage, read_write> info: array<u32>; |
| |
| @group(0) @binding(6) |
| var<storage, read_write> clip_inp: array<ClipInp>; |
| |
| let WG_SIZE = 256u; |
| |
| fn read_transform(transform_base: u32, ix: u32) -> Transform { |
| let base = transform_base + ix * 6u; |
| let c0 = bitcast<f32>(scene[base]); |
| let c1 = bitcast<f32>(scene[base + 1u]); |
| let c2 = bitcast<f32>(scene[base + 2u]); |
| let c3 = bitcast<f32>(scene[base + 3u]); |
| let c4 = bitcast<f32>(scene[base + 4u]); |
| let c5 = bitcast<f32>(scene[base + 5u]); |
| let matrx = vec4(c0, c1, c2, c3); |
| let translate = vec2(c4, c5); |
| return Transform(matrx, translate); |
| } |
| |
| var<workgroup> sh_scratch: array<DrawMonoid, WG_SIZE>; |
| |
| @compute @workgroup_size(256) |
| fn main( |
| @builtin(global_invocation_id) global_id: vec3<u32>, |
| @builtin(local_invocation_id) local_id: vec3<u32>, |
| @builtin(workgroup_id) wg_id: vec3<u32>, |
| ) { |
| let ix = global_id.x; |
| // Reduce prefix of workgroups up to this one |
| var agg = draw_monoid_identity(); |
| if local_id.x < wg_id.x { |
| agg = reduced[local_id.x]; |
| } |
| sh_scratch[local_id.x] = agg; |
| for (var i = 0u; i < firstTrailingBit(WG_SIZE); i += 1u) { |
| workgroupBarrier(); |
| if local_id.x + (1u << i) < WG_SIZE { |
| let other = sh_scratch[local_id.x + (1u << i)]; |
| agg = combine_draw_monoid(agg, other); |
| } |
| workgroupBarrier(); |
| sh_scratch[local_id.x] = agg; |
| } |
| // Two barriers can be eliminated if we use separate shared arrays |
| // for prefix and intra-workgroup prefix sum. |
| workgroupBarrier(); |
| var m = sh_scratch[0]; |
| workgroupBarrier(); |
| let tag_word = scene[config.drawtag_base + ix]; |
| agg = map_draw_tag(tag_word); |
| sh_scratch[local_id.x] = agg; |
| for (var i = 0u; i < firstTrailingBit(WG_SIZE); i += 1u) { |
| workgroupBarrier(); |
| if local_id.x >= 1u << i { |
| let other = sh_scratch[local_id.x - (1u << i)]; |
| agg = combine_draw_monoid(agg, other); |
| } |
| workgroupBarrier(); |
| sh_scratch[local_id.x] = agg; |
| } |
| workgroupBarrier(); |
| if local_id.x > 0u { |
| m = combine_draw_monoid(m, sh_scratch[local_id.x - 1u]); |
| } |
| // m now contains exclusive prefix sum of draw monoid |
| draw_monoid[ix] = m; |
| let dd = config.drawdata_base + m.scene_offset; |
| let di = m.info_offset; |
| if tag_word == DRAWTAG_FILL_COLOR || tag_word == DRAWTAG_FILL_LIN_GRADIENT || |
| tag_word == DRAWTAG_FILL_RAD_GRADIENT || tag_word == DRAWTAG_FILL_IMAGE || |
| tag_word == DRAWTAG_BEGIN_CLIP |
| { |
| let bbox = path_bbox[m.path_ix]; |
| // TODO: bbox is mostly yagni here, sort that out. Maybe clips? |
| // let x0 = f32(bbox.x0); |
| // let y0 = f32(bbox.y0); |
| // let x1 = f32(bbox.x1); |
| // let y1 = f32(bbox.y1); |
| // let bbox_f = vec4(x0, y0, x1, y1); |
| let fill_mode = u32(bbox.linewidth >= 0.0); |
| var transform = Transform(); |
| var linewidth = bbox.linewidth; |
| if linewidth >= 0.0 || tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT || |
| tag_word == DRAWTAG_FILL_IMAGE |
| { |
| transform = read_transform(config.transform_base, bbox.trans_ix); |
| } |
| if linewidth >= 0.0 { |
| // Note: doesn't deal with anisotropic case |
| let matrx = transform.matrx; |
| linewidth *= sqrt(abs(matrx.x * matrx.w - matrx.y * matrx.z)); |
| } |
| switch tag_word { |
| // DRAWTAG_FILL_COLOR |
| case 0x44u: { |
| info[di] = bitcast<u32>(linewidth); |
| } |
| // DRAWTAG_FILL_LIN_GRADIENT |
| case 0x114u: { |
| info[di] = bitcast<u32>(linewidth); |
| var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u])); |
| var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u])); |
| p0 = transform_apply(transform, p0); |
| p1 = transform_apply(transform, p1); |
| let dxy = p1 - p0; |
| let scale = 1.0 / dot(dxy, dxy); |
| let line_xy = dxy * scale; |
| let line_c = -dot(p0, line_xy); |
| info[di + 1u] = bitcast<u32>(line_xy.x); |
| info[di + 2u] = bitcast<u32>(line_xy.y); |
| info[di + 3u] = bitcast<u32>(line_c); |
| } |
| // DRAWTAG_FILL_RAD_GRADIENT |
| case 0x29cu: { |
| // Two-point conical gradient implementation based |
| // on the algorithm at <https://skia.org/docs/dev/design/conical/> |
| // This epsilon matches what Skia uses |
| let GRADIENT_EPSILON = 1.0 / f32(1 << 12u); |
| info[di] = bitcast<u32>(linewidth); |
| var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u])); |
| var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u])); |
| var r0 = bitcast<f32>(scene[dd + 5u]); |
| var r1 = bitcast<f32>(scene[dd + 6u]); |
| let user_to_gradient = transform_inverse(transform); |
| // Output variables |
| var xform = Transform(); |
| var focal_x = 0.0; |
| var radius = 0.0; |
| var kind = 0u; |
| var flags = 0u; |
| if abs(r0 - r1) <= GRADIENT_EPSILON { |
| // When the radii are the same, emit a strip gradient |
| kind = RAD_GRAD_KIND_STRIP; |
| let scaled = r0 / distance(p0, p1); |
| xform = transform_mul( |
| two_point_to_unit_line(p0, p1), |
| user_to_gradient |
| ); |
| radius = scaled * scaled; |
| } else { |
| // Assume a two point conical gradient unless the centers |
| // are equal. |
| kind = RAD_GRAD_KIND_CONE; |
| if all(p0 == p1) { |
| kind = RAD_GRAD_KIND_CIRCULAR; |
| // Nudge p0 a bit to avoid denormals. |
| p0 += GRADIENT_EPSILON; |
| } |
| if r1 == 0.0 { |
| // If r1 == 0.0, swap the points and radii |
| flags |= RAD_GRAD_SWAPPED; |
| let tmp_p = p0; |
| p0 = p1; |
| p1 = tmp_p; |
| let tmp_r = r0; |
| r0 = r1; |
| r1 = tmp_r; |
| } |
| focal_x = r0 / (r0 - r1); |
| let cf = (1.0 - focal_x) * p0 + focal_x * p1; |
| radius = r1 / (distance(cf, p1)); |
| let user_to_unit_line = transform_mul( |
| two_point_to_unit_line(cf, p1), |
| user_to_gradient |
| ); |
| var user_to_scaled = user_to_unit_line; |
| // When r == 1.0, focal point is on circle |
| if abs(radius - 1.0) <= GRADIENT_EPSILON { |
| kind = RAD_GRAD_KIND_FOCAL_ON_CIRCLE; |
| let scale = 0.5 * abs(1.0 - focal_x); |
| user_to_scaled = transform_mul( |
| Transform(vec4(scale, 0.0, 0.0, scale), vec2(0.0)), |
| user_to_unit_line |
| ); |
| } else { |
| let a = radius * radius - 1.0; |
| let scale_ratio = abs(1.0 - focal_x) / a; |
| let scale_x = radius * scale_ratio; |
| let scale_y = sqrt(abs(a)) * scale_ratio; |
| user_to_scaled = transform_mul( |
| Transform(vec4(scale_x, 0.0, 0.0, scale_y), vec2(0.0)), |
| user_to_unit_line |
| ); |
| } |
| xform = user_to_scaled; |
| } |
| info[di + 1u] = bitcast<u32>(xform.matrx.x); |
| info[di + 2u] = bitcast<u32>(xform.matrx.y); |
| info[di + 3u] = bitcast<u32>(xform.matrx.z); |
| info[di + 4u] = bitcast<u32>(xform.matrx.w); |
| info[di + 5u] = bitcast<u32>(xform.translate.x); |
| info[di + 6u] = bitcast<u32>(xform.translate.y); |
| info[di + 7u] = bitcast<u32>(focal_x); |
| info[di + 8u] = bitcast<u32>(radius); |
| info[di + 9u] = bitcast<u32>((flags << 3u) | kind); |
| } |
| // DRAWTAG_FILL_IMAGE |
| case 0x248u: { |
| info[di] = bitcast<u32>(linewidth); |
| let inv = transform_inverse(transform); |
| info[di + 1u] = bitcast<u32>(inv.matrx.x); |
| info[di + 2u] = bitcast<u32>(inv.matrx.y); |
| info[di + 3u] = bitcast<u32>(inv.matrx.z); |
| info[di + 4u] = bitcast<u32>(inv.matrx.w); |
| info[di + 5u] = bitcast<u32>(inv.translate.x); |
| info[di + 6u] = bitcast<u32>(inv.translate.y); |
| info[di + 7u] = scene[dd]; |
| info[di + 8u] = scene[dd + 1u]; |
| } |
| default: {} |
| } |
| } |
| if tag_word == DRAWTAG_BEGIN_CLIP || tag_word == DRAWTAG_END_CLIP { |
| var path_ix = ~ix; |
| if tag_word == DRAWTAG_BEGIN_CLIP { |
| path_ix = m.path_ix; |
| } |
| clip_inp[m.clip_ix] = ClipInp(ix, i32(path_ix)); |
| } |
| } |
| |
| fn two_point_to_unit_line(p0: vec2<f32>, p1: vec2<f32>) -> Transform { |
| let tmp1 = from_poly2(p0, p1); |
| let inv = transform_inverse(tmp1); |
| let tmp2 = from_poly2(vec2(0.0), vec2(1.0, 0.0)); |
| return transform_mul(tmp2, inv); |
| } |
| |
| fn from_poly2(p0: vec2<f32>, p1: vec2<f32>) -> Transform { |
| return Transform( |
| vec4(p1.y - p0.y, p0.x - p1.x, p1.x - p0.x, p1.y - p0.y), |
| vec2(p0.x, p0.y) |
| ); |
| } |