2022-11-04 10:53:34 +11:00
|
|
|
// Copyright 2022 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
// Also licensed under MIT license, at your choice.
|
|
|
|
|
|
|
|
// Path segment decoding for the full case.
|
|
|
|
|
|
|
|
// In the simple case, path segments are decoded as part of the coarse
|
|
|
|
// path rendering stage. In the full case, they are separated, as the
|
|
|
|
// decoding process also generates bounding boxes, and those in turn are
|
|
|
|
// used for tile allocation and clipping; actual coarse path rasterization
|
|
|
|
// can't proceed until those are complete.
|
|
|
|
|
|
|
|
// There's some duplication of the decoding code but we won't worry about
|
|
|
|
// that just now. Perhaps it could be factored more nicely later.
|
|
|
|
|
|
|
|
#import config
|
|
|
|
#import pathtag
|
2022-11-04 13:33:11 +11:00
|
|
|
#import cubic
|
2022-11-04 10:53:34 +11:00
|
|
|
|
|
|
|
@group(0) @binding(0)
|
|
|
|
var<storage> config: Config;
|
|
|
|
|
|
|
|
@group(0) @binding(1)
|
|
|
|
var<storage> scene: array<u32>;
|
|
|
|
|
|
|
|
@group(0) @binding(2)
|
|
|
|
var<storage> tag_monoids: array<TagMonoid>;
|
|
|
|
|
|
|
|
struct AtomicPathBbox {
|
|
|
|
x0: atomic<i32>,
|
|
|
|
y0: atomic<i32>,
|
|
|
|
x1: atomic<i32>,
|
|
|
|
y1: atomic<i32>,
|
|
|
|
linewidth: f32,
|
|
|
|
trans_ix: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
@group(0) @binding(3)
|
|
|
|
var<storage, read_write> path_bboxes: array<AtomicPathBbox>;
|
|
|
|
|
|
|
|
|
|
|
|
@group(0) @binding(4)
|
|
|
|
var<storage, read_write> cubics: array<Cubic>;
|
|
|
|
|
|
|
|
// Monoid is yagni, for future optimization
|
|
|
|
|
|
|
|
// struct BboxMonoid {
|
|
|
|
// bbox: vec4<f32>,
|
|
|
|
// flags: u32,
|
|
|
|
// }
|
|
|
|
|
|
|
|
// let FLAG_RESET_BBOX = 1u;
|
|
|
|
// let FLAG_SET_BBOX = 2u;
|
|
|
|
|
|
|
|
// fn combine_bbox_monoid(a: BboxMonoid, b: BboxMonoid) -> BboxMonoid {
|
|
|
|
// var c: BboxMonoid;
|
|
|
|
// c.bbox = b.bbox;
|
|
|
|
// // TODO: previous-me thought this should be gated on b & SET_BBOX == false also
|
|
|
|
// if (a.flags & FLAG_RESET_BBOX) == 0u && b.bbox.z <= b.bbox.x && b.bbox.w <= b.bbox.y {
|
|
|
|
// c.bbox = a.bbox;
|
|
|
|
// } else if (a.flags & FLAG_RESET_BBOX) == 0u && (b.flags & FLAG_SET_BBOX) == 0u ||
|
|
|
|
// (a.bbox.z > a.bbox.x || a.bbox.w > a.bbox.y)
|
|
|
|
// {
|
|
|
|
// c.bbox = vec4<f32>(min(a.bbox.xy, c.bbox.xy), max(a.bbox.xw, c.bbox.zw));
|
|
|
|
// }
|
|
|
|
// c.flags = (a.flags & FLAG_SET_BBOX) | b.flags;
|
|
|
|
// c.flags |= (a.flags & FLAG_RESET_BBOX) << 1u;
|
|
|
|
// return c;
|
|
|
|
// }
|
|
|
|
|
|
|
|
// fn bbox_monoid_identity() -> BboxMonoid {
|
|
|
|
// return BboxMonoid();
|
|
|
|
// }
|
|
|
|
|
|
|
|
var<private> pathdata_base: u32;
|
|
|
|
|
|
|
|
fn read_f32_point(ix: u32) -> vec2<f32> {
|
|
|
|
let x = bitcast<f32>(scene[pathdata_base + ix]);
|
|
|
|
let y = bitcast<f32>(scene[pathdata_base + ix + 1u]);
|
|
|
|
return vec2<f32>(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read_i16_point(ix: u32) -> vec2<f32> {
|
|
|
|
let raw = scene[pathdata_base + ix];
|
|
|
|
let x = f32(i32(raw << 16u) >> 16u);
|
|
|
|
let y = f32(i32(raw) >> 16u);
|
|
|
|
return vec2<f32>(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Transform {
|
|
|
|
matrx: vec4<f32>,
|
|
|
|
translate: vec2<f32>,
|
|
|
|
}
|
|
|
|
|
2022-11-04 13:33:11 +11:00
|
|
|
fn read_transform(transform_base: u32, ix: u32) -> Transform {
|
2022-11-04 10:53:34 +11:00
|
|
|
let base = transform_base + ix * 6u;
|
|
|
|
let c0 = bitcast<f32>(scene[base]);
|
2022-11-04 16:00:52 +11:00
|
|
|
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]);
|
2022-11-04 10:53:34 +11:00
|
|
|
let matrx = vec4<f32>(c0, c1, c2, c3);
|
|
|
|
let translate = vec2<f32>(c4, c5);
|
|
|
|
return Transform(matrx, translate);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn transform_apply(transform: Transform, p: vec2<f32>) -> vec2<f32> {
|
|
|
|
return transform.matrx.xy * p.x + transform.matrx.zw * p.y + transform.translate;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn round_down(x: f32) -> i32 {
|
|
|
|
return i32(floor(x));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn round_up(x: f32) -> i32 {
|
|
|
|
return i32(ceil(x));
|
|
|
|
}
|
|
|
|
|
|
|
|
@compute @workgroup_size(256)
|
|
|
|
fn main(
|
|
|
|
@builtin(global_invocation_id) global_id: vec3<u32>,
|
|
|
|
@builtin(local_invocation_id) local_id: vec3<u32>,
|
|
|
|
) {
|
|
|
|
let ix = global_id.x;
|
|
|
|
let tag_word = scene[config.pathtag_base + (ix >> 2u)];
|
|
|
|
pathdata_base = config.pathdata_base;
|
|
|
|
let shift = (ix & 3u) * 8u;
|
|
|
|
var tm = reduce_tag(tag_word & ((1u << shift) - 1u));
|
|
|
|
tm = combine_tag_monoid(tag_monoids[ix >> 2u], tm);
|
|
|
|
var tag_byte = (tag_word >> shift) & 0xffu;
|
|
|
|
|
|
|
|
let out = &path_bboxes[tm.path_ix];
|
2022-11-05 06:40:54 +11:00
|
|
|
var linewidth: f32;
|
2022-11-04 10:53:34 +11:00
|
|
|
if (tag_byte & PATH_TAG_PATH) != 0u {
|
2022-11-05 06:40:54 +11:00
|
|
|
linewidth = bitcast<f32>(scene[config.linewidth_base + tm.linewidth_ix]);
|
|
|
|
(*out).linewidth = linewidth;
|
2022-11-04 10:53:34 +11:00
|
|
|
(*out).trans_ix = tm.trans_ix;
|
|
|
|
}
|
|
|
|
// Decode path data
|
|
|
|
let seg_type = tag_byte & PATH_TAG_SEG_TYPE;
|
|
|
|
if seg_type != 0u {
|
|
|
|
var p0: vec2<f32>;
|
|
|
|
var p1: vec2<f32>;
|
|
|
|
var p2: vec2<f32>;
|
|
|
|
var p3: vec2<f32>;
|
|
|
|
if (tag_byte & PATH_TAG_F32) != 0u {
|
|
|
|
p0 = read_f32_point(tm.pathseg_offset);
|
|
|
|
p1 = read_f32_point(tm.pathseg_offset + 2u);
|
|
|
|
if seg_type >= PATH_TAG_QUADTO {
|
|
|
|
p2 = read_f32_point(tm.pathseg_offset + 4u);
|
|
|
|
if seg_type == PATH_TAG_CUBICTO {
|
|
|
|
p3 = read_f32_point(tm.pathseg_offset + 6u);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p0 = read_i16_point(tm.pathseg_offset);
|
|
|
|
p1 = read_i16_point(tm.pathseg_offset + 1u);
|
|
|
|
if seg_type >= PATH_TAG_QUADTO {
|
|
|
|
p2 = read_i16_point(tm.pathseg_offset + 2u);
|
|
|
|
if seg_type == PATH_TAG_CUBICTO {
|
|
|
|
p3 = read_i16_point(tm.pathseg_offset + 3u);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-04 13:33:11 +11:00
|
|
|
let transform = read_transform(config.transform_base, tm.trans_ix);
|
2022-11-04 16:00:52 +11:00
|
|
|
//let transform = Transform(vec4<f32>(1.0, 0.0, 0.0, 1.0), vec2<f32>());
|
2022-11-04 10:53:34 +11:00
|
|
|
p0 = transform_apply(transform, p0);
|
|
|
|
p1 = transform_apply(transform, p1);
|
|
|
|
var bbox = vec4<f32>(min(p0, p1), max(p0, p1));
|
|
|
|
// Degree-raise
|
|
|
|
if seg_type == PATH_TAG_LINETO {
|
|
|
|
p3 = p1;
|
|
|
|
p2 = mix(p3, p0, 1.0 / 3.0);
|
|
|
|
p1 = mix(p0, p3, 1.0 / 3.0);
|
|
|
|
} else if seg_type >= PATH_TAG_QUADTO {
|
|
|
|
p2 = transform_apply(transform, p2);
|
|
|
|
bbox = vec4<f32>(min(bbox.xy, p2), max(bbox.zw, p2));
|
|
|
|
if seg_type == PATH_TAG_CUBICTO {
|
|
|
|
p3 = transform_apply(transform, p3);
|
|
|
|
bbox = vec4<f32>(min(bbox.xy, p3), max(bbox.zw, p3));
|
|
|
|
} else {
|
|
|
|
p3 = p2;
|
|
|
|
p2 = mix(p1, p2, 1.0 / 3.0);
|
|
|
|
p1 = mix(p1, p0, 1.0 / 3.0);
|
|
|
|
}
|
|
|
|
}
|
2022-11-05 06:40:54 +11:00
|
|
|
if linewidth >= 0.0 {
|
|
|
|
// See https://www.iquilezles.org/www/articles/ellipses/ellipses.htm
|
|
|
|
// This is the correct bounding box, but we're not handling rendering
|
|
|
|
// in the isotropic case, so it may mismatch.
|
|
|
|
let stroke = 0.5 * linewidth * vec2<f32>(length(transform.matrx.xz), length(transform.matrx.yw));
|
|
|
|
bbox += vec4<f32>(-stroke, stroke);
|
|
|
|
}
|
2022-11-04 13:33:11 +11:00
|
|
|
cubics[global_id.x] = Cubic(p0, p1, p2, p3, tm.path_ix, 0u);
|
2022-11-04 10:53:34 +11:00
|
|
|
// Update bounding box using atomics only. Computing a monoid is a
|
|
|
|
// potential future optimization.
|
2022-11-04 13:33:11 +11:00
|
|
|
if bbox.z > bbox.x || bbox.w > bbox.y {
|
2022-11-04 10:53:34 +11:00
|
|
|
atomicMin(&(*out).x0, round_down(bbox.x));
|
|
|
|
atomicMin(&(*out).y0, round_down(bbox.y));
|
|
|
|
atomicMax(&(*out).x1, round_up(bbox.z));
|
|
|
|
atomicMax(&(*out).y1, round_up(bbox.w));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|