blob: 07fad371bc575ee5c2712cab32736547ca3a4fd7 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
let TILE_WIDTH = 16u;
let TILE_HEIGHT = 4u;
let TILE_WIDTH_SHIFT = 4u;
let TILE_HEIGHT_SHIFT = 2u;
let MAX_WIDTH_SHIFT = 16u;
let MAX_HEIGHT_SHIFT = 15u;
let BLOCK_LEN = 64u;
let BLOCK_SHIFT = 6u;
let BLOCK_MASK = 63u;
let QUEUES_LEN = 128u;
let QUEUES_MASK = 127u;
let PIXEL_WIDTH = 16;
let PIXEL_DOUBLE_AREA_RECIP = 0.001953125;
let LAYER_ID_NONE = 4294967295u;
struct PixelSegment {
lo: u32,
hi: u32,
}
let LAYER_ID_BIT_SIZE = 21u;
let DOUBLE_AREA_MULTIPLIER_BIT_SIZE = 6u;
let COVER_BIT_SIZE = 6u;
fn pixelSegmentTileX(seg: PixelSegment) -> i32 {
return extractBits(
i32(seg.hi),
32u - (MAX_WIDTH_SHIFT - TILE_WIDTH_SHIFT) -
(MAX_HEIGHT_SHIFT - TILE_HEIGHT_SHIFT),
MAX_WIDTH_SHIFT - TILE_WIDTH_SHIFT,
) - 1;
}
fn pixelSegmentTileY(seg: PixelSegment) -> i32 {
return extractBits(
i32(seg.hi),
32u - (MAX_HEIGHT_SHIFT - TILE_HEIGHT_SHIFT),
MAX_HEIGHT_SHIFT - TILE_HEIGHT_SHIFT,
) - 1;
}
fn pixelSegmentLayerId(seg: PixelSegment) -> u32 {
let lo = extractBits(
seg.lo,
TILE_WIDTH_SHIFT + TILE_HEIGHT_SHIFT +
DOUBLE_AREA_MULTIPLIER_BIT_SIZE + COVER_BIT_SIZE,
32u - TILE_WIDTH_SHIFT - TILE_HEIGHT_SHIFT -
DOUBLE_AREA_MULTIPLIER_BIT_SIZE - COVER_BIT_SIZE,
);
return insertBits(
lo,
seg.hi,
32u - TILE_WIDTH_SHIFT - TILE_HEIGHT_SHIFT -
DOUBLE_AREA_MULTIPLIER_BIT_SIZE - COVER_BIT_SIZE,
32u - (MAX_WIDTH_SHIFT - TILE_WIDTH_SHIFT) -
(MAX_HEIGHT_SHIFT - TILE_HEIGHT_SHIFT),
);
}
fn pixelSegmentLocalX(seg: PixelSegment) -> u32 {
return extractBits(
seg.lo,
TILE_HEIGHT_SHIFT + DOUBLE_AREA_MULTIPLIER_BIT_SIZE + COVER_BIT_SIZE,
TILE_WIDTH_SHIFT,
);
}
fn pixelSegmentLocalY(seg: PixelSegment) -> u32 {
return extractBits(
seg.lo,
DOUBLE_AREA_MULTIPLIER_BIT_SIZE + COVER_BIT_SIZE,
TILE_HEIGHT_SHIFT,
);
}
fn pixelSegmentDoubleAreaMultiplier(seg: PixelSegment) -> u32 {
return extractBits(
seg.lo,
COVER_BIT_SIZE,
DOUBLE_AREA_MULTIPLIER_BIT_SIZE,
);
}
fn pixelSegmentCover(seg: PixelSegment) -> i32 {
return extractBits(i32(seg.lo), 0u, COVER_BIT_SIZE);
}
struct OptimizedSegment {
lo: u32,
hi: u32,
}
let DOUBLE_AREA_BIT_SIZE = 12u;
let DOUBLE_AREA_OFFSET = 20u;
let COVER_OFFSET = 26u;
fn optimizedSegment(
tile_x: i32,
layer_id: u32,
local_x: u32,
local_y: u32,
double_area: i32,
cover: i32,
) -> OptimizedSegment {
var lo = local_y;
lo = insertBits(lo, local_x, TILE_HEIGHT_SHIFT, TILE_WIDTH_SHIFT);
lo = u32(insertBits(
i32(lo),
tile_x,
TILE_WIDTH_SHIFT + TILE_HEIGHT_SHIFT,
MAX_WIDTH_SHIFT - TILE_WIDTH_SHIFT,
));
lo = u32(insertBits(
i32(lo),
double_area,
DOUBLE_AREA_OFFSET,
DOUBLE_AREA_BIT_SIZE,
));
var hi = layer_id;
hi = u32(insertBits(i32(hi), cover, COVER_OFFSET, COVER_BIT_SIZE));
return OptimizedSegment(lo, hi);
}
fn optimizedSegmentTileX(seg: OptimizedSegment) -> i32 {
return extractBits(
i32(seg.lo),
TILE_WIDTH_SHIFT + TILE_HEIGHT_SHIFT,
MAX_WIDTH_SHIFT - TILE_WIDTH_SHIFT,
);
}
fn optimizedSegmentLayerId(seg: OptimizedSegment) -> u32 {
return extractBits(seg.hi, 0u, LAYER_ID_BIT_SIZE);
}
fn optimizedSegmentLocalX(seg: OptimizedSegment) -> u32 {
return extractBits(seg.lo, TILE_HEIGHT_SHIFT, TILE_WIDTH_SHIFT);
}
fn optimizedSegmentLocalY(seg: OptimizedSegment) -> u32 {
return extractBits(seg.lo, 0u, TILE_HEIGHT_SHIFT);
}
fn optimizedSegmentDoubleArea(seg: OptimizedSegment) -> i32 {
return extractBits(i32(seg.lo), DOUBLE_AREA_OFFSET, DOUBLE_AREA_BIT_SIZE);
}
fn optimizedSegmentCover(seg: OptimizedSegment) -> i32 {
return extractBits(i32(seg.hi), COVER_OFFSET, COVER_BIT_SIZE);
}
struct Color {
r: f32,
g: f32,
b: f32,
a: f32,
}
struct Config {
segments_len: u32,
width: u32,
height: u32,
_padding: u32,
clear_color: Color,
}
struct Style {
fill_rule: u32,
color: Color,
blend_mode: u32,
}
@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<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;
}
let i = block_index * BLOCK_LEN + local_index;
var opt_seg = optimizedSegment(
-2,
0u,
0u,
0u,
0,
0,
);
workgroupBarrier();
if i < config.segments_len {
let seg = segments[i];
if pixelSegmentTileY(seg) == tile_y {
let cover = pixelSegmentCover(seg);
let double_area = i32(pixelSegmentDoubleAreaMultiplier(seg)) * cover;
opt_seg = optimizedSegment(
pixelSegmentTileX(seg),
pixelSegmentLayerId(seg),
pixelSegmentLocalX(seg),
pixelSegmentLocalY(seg),
double_area,
cover,
);
}
}
segment_block[local_index] = opt_seg;
workgroupBarrier();
block_index++;
return true;
}
fn clearColor() -> vec4<f32> {
return vec4(
config.clear_color.r,
config.clear_color.g,
config.clear_color.b,
config.clear_color.a,
);
}
var<workgroup> queues_layer_id_buffer: array<u32, QUEUES_LEN>;
var<workgroup> queues_cover_buffer: array<atomic<u32>, QUEUES_LEN>;
struct Queues {
start0: u32,
end0: u32,
start1: u32,
}
struct Painter {
queues: Queues,
double_area: i32,
cover: i32,
color: vec4<f32>,
}
fn areaToCoverage(double_area: i32, fill_rule: u32) -> f32 {
switch fill_rule {
// NonZero
case 0u {
return clamp(abs(f32(double_area) * PIXEL_DOUBLE_AREA_RECIP), 0.0, 1.0);
}
// EvenOdd
default {
let winding_number = double_area >> 9u;
let norm = f32(double_area & 511) * PIXEL_DOUBLE_AREA_RECIP;
return select(
1.0 - norm,
norm,
(winding_number & 1) == 0,
);
}
}
}
fn lum(color: vec3<f32>) -> f32 {
return fma(
color.r,
0.3,
fma(color.g, 0.59, color.b * 0.11),
);
}
fn sat(color: vec3<f32>) -> f32 {
return max(color.r, max(color.g, color.b)) -
min(color.r, min(color.g, color.b));
}
fn clipColor(color: vec3<f32>) -> vec3<f32> {
let l = lum(color);
let n = min(color.r, min(color.g, color.b));
let x = max(color.r, max(color.g, color.b));
let l_1 = l - 1.0;
let x_l_recip = 1.0 / (x - l);
let l_n_recip_l = 1.0 / (l - n) * l;
return select(
select(
color,
fma(
vec3(l_n_recip_l),
color - vec3(l),
vec3(l),
),
n < 0.0,
),
fma(
vec3(x_l_recip),
fma(
vec3(l),
vec3(l_1) - color,
color,
),
vec3(l),
),
x > 1.0,
);
}
fn setLum(color: vec3<f32>, l: f32) -> vec3<f32> {
let d = l - lum(color);
return clipColor(color + vec3(d));
}
fn setSat(color: vec3<f32>, s: f32) -> vec3<f32> {
let c_min = min(color.r, min(color.g, color.b));
let c_max = max(color.r, max(color.g, color.b));
let c_mid = color.r + color.g + color.b - c_min - c_max;
let min_lt_max = c_min < c_max;
let s_mid = select(
0.0,
fma(s, -c_min, s * c_mid) / (c_max - c_min),
min_lt_max,
);
let s_max = select(0.0, s, min_lt_max);
return select(
select(vec3(s_mid), vec3(0.0), color == vec3(c_min)),
vec3(s_max),
color == vec3(c_max),
);
}
fn blend(dst: vec4<f32>, src: vec4<f32>, blend_mode: u32) -> vec4<f32> {
let inv_dst_a = 1.0 - dst.a;
let inv_dst_a_src_a = inv_dst_a * src.a;
let inv_src_a = 1.0 - src.a;
let dst_a_src_a = dst.a * src.a;
var color: vec3<f32>;
switch blend_mode {
// Over
case 0u {
color = src.rgb;
}
// Multiply
case 1u {
color = dst.rgb * src.rgb;
}
// Screen
case 2u {
color = fma(dst.rgb, -src.rgb, dst.rgb) + src.rgb;
}
// Overlay
case 3u {
color = 2.0 * select(
(dst.rgb + src.rgb -
fma(dst.rgb, src.rgb, vec3(0.5))),
dst.rgb * src.rgb,
dst.rgb <= vec3(0.5),
);
}
// Darken
case 4u {
color = min(dst.rgb, src.rgb);
}
// Lighten
case 5u {
color = max(dst.rgb, src.rgb);
}
// ColorDodge
case 6u {
color = select(
min(vec3(1.0), dst.rgb / (vec3(1.0) - src.rgb)),
vec3(1.0),
src.rgb == vec3(1.0),
);
}
// ColorBurn
case 7u {
color = select(
vec3(1.0) - min(
vec3(1.0),
(vec3(1.0) - dst.rgb) / src.rgb,
),
vec3(0.0),
src.rgb == vec3(0.0),
);
}
// HardLight
case 8u {
color = 2.0 * select(
dst.rgb + src.rgb -
fma(dst.rgb, src.rgb, vec3(0.5)),
dst.rgb * src.rgb,
src.rgb <= vec3(0.5),
);
}
// SoftLight
case 9u {
let d = select(
sqrt(dst.rgb),
dst.rgb * fma(
fma(vec3(16.0), dst.rgb, vec3(-12.0)),
dst.rgb,
vec3(4.0),
),
dst.rgb <= vec3(0.25),
);
color = select(
fma(
d - dst.rgb,
fma(vec3(2.0), src.rgb, vec3(-1.0)),
dst.rgb,
),
dst.rgb * (vec3(1.0) - dst.rgb),
src.rgb <= vec3(0.5),
);
}
// Difference
case 10u {
color = abs(dst.rgb - src.rgb);
}
// Exclusion
case 11u {
color = fma(
dst.rgb,
fma(vec3(-2.0), src.rgb, vec3(1.0)),
src.rgb,
);
}
// Hue
case 12u {
color = setLum(setSat(src.rgb, sat(dst.rgb)), lum(dst.rgb));
}
// Saturation
case 13u {
color = setLum(setSat(dst.rgb, sat(src.rgb)), lum(dst.rgb));
}
// Color
case 14u {
color = setLum(src.rgb, lum(dst.rgb));
}
// Luminosity
default {
color = setLum(dst.rgb, lum(src.rgb));
}
}
let current = fma(src.rgb, vec3(inv_dst_a_src_a), color.rgb * dst_a_src_a);
return fma(dst, vec4(inv_src_a), vec4(current, src.a));
}
fn painterPushCover(
painter: ptr<function, Painter>,
layer_id: u32,
style_header: u32,
local_id: vec2<u32>,
) {
var mask: u32;
switch getFillRule(style_header) {
// NonZero
case 0u {
mask = 4294967295u;
}
// EvenOdd
default {
mask = 522133279u;
}
}
queues_layer_id_buffer[(*painter).queues.start1] = layer_id;
if local_id.x == 0u && local_id.y == 0u {
atomicStore(&queues_cover_buffer[(*painter).queues.start1], 0u);
}
workgroupBarrier();
if local_id.x == (TILE_WIDTH - 1u) {
let _ = atomicOr(
&queues_cover_buffer[(*painter).queues.start1],
u32(((*painter).cover & 255) << (local_id.y << 3u)),
);
}
workgroupBarrier();
(*painter).queues.start1 = ((*painter).queues.start1 + select(
0u,
1u,
(atomicLoad(&queues_cover_buffer[(*painter).queues.start1]) & mask) !=
0u,
)) & QUEUES_MASK;
}
fn painterBlendLayer(
painter: ptr<function, Painter>,
layer_id: u32,
pixel_coords: vec2<u32>,
local_id: vec2<u32>,
) {
let style_offset = style_indices[layer_id];
let style_header = styles[style_offset];
painterPushCover(painter, layer_id, style_header, local_id);
var src: vec4<f32>;
// 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, 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 {
let current_layer_id =
queues_layer_id_buffer[(*painter).queues.start0];
if (current_layer_id > layer_id) { break; }
let shift = local_id.y << 3u;
let cover = i32(queues_cover_buffer[(*painter).queues.start0]) <<
(24u - shift) >> 24u;
(*painter).double_area += cover * 2 * PIXEL_WIDTH;
(*painter).cover += cover;
if current_layer_id < layer_id {
painterBlendLayer(painter, current_layer_id, pixel_coords, local_id);
}
(*painter).queues.start0 = ((*painter).queues.start0 + 1u) &
QUEUES_MASK;
}
}
fn painterNegativeCovers(
painter: ptr<function, Painter>,
tile: vec2<i32>,
local_index: u32,
local_id: vec2<u32>,
) {
var seg: OptimizedSegment;
var layer_id = LAYER_ID_NONE;
loop {
var should_break = false;
loop {
seg = segment_block[segment_index];
should_break = optimizedSegmentTileX(seg) != tile.x;
if should_break || segment_index == BLOCK_LEN { break; }
segment_index += 1u;
let current_layer_id = optimizedSegmentLayerId(seg);
if current_layer_id != layer_id {
if layer_id != LAYER_ID_NONE {
let style_header = styles[style_indices[layer_id]];
painterPushCover(
painter,
layer_id,
style_header,
local_id,
);
(*painter).cover = 0;
}
layer_id = current_layer_id;
}
let cover = select(
0,
optimizedSegmentCover(seg),
optimizedSegmentLocalY(seg) == local_id.y,
);
(*painter).cover += cover;
}
if segment_index == BLOCK_LEN {
should_break = !loadSegments(tile.y, local_index);
segment_index = 0u;
}
if should_break {
if layer_id != LAYER_ID_NONE {
let style_header = styles[style_indices[layer_id]];
painterPushCover(painter, layer_id, style_header, local_id);
(*painter).cover = 0;
}
break;
}
}
}
fn painterPaintTile(
painter: ptr<function, Painter>,
tile: vec2<i32>,
local_index: u32,
pixel_coords: vec2<u32>,
local_id: vec2<u32>,
) {
var seg: OptimizedSegment;
var layer_id = LAYER_ID_NONE;
loop {
var should_break = false;
loop {
seg = segment_block[segment_index];
should_break = optimizedSegmentTileX(seg) != tile.x;
if should_break || segment_index == BLOCK_LEN { break; }
segment_index += 1u;
let current_layer_id = optimizedSegmentLayerId(seg);
if current_layer_id != layer_id {
if layer_id != LAYER_ID_NONE {
painterBlendLayer(painter, layer_id, pixel_coords, local_id);
}
painterPopQueueUntil(painter, current_layer_id, pixel_coords, local_id);
layer_id = current_layer_id;
}
let local_x = optimizedSegmentLocalX(seg);
let local_y = optimizedSegmentLocalY(seg);
(*painter).double_area += select(
0,
optimizedSegmentDoubleArea(seg),
local_id.x == local_x && local_id.y == local_y,
);
let cover = optimizedSegmentCover(seg);
(*painter).double_area += 2 * PIXEL_WIDTH * select(
0,
cover,
local_id.x > local_x && local_id.y == local_y,
);
(*painter).cover += select(
0,
cover,
local_id.y == local_y,
);
}
if segment_index == BLOCK_LEN {
should_break = !loadSegments(tile.y, local_index);
segment_index = 0u;
}
if should_break {
if layer_id != LAYER_ID_NONE {
painterBlendLayer(painter, layer_id, pixel_coords, local_id);
}
painterPopQueueUntil(painter, LAYER_ID_NONE, pixel_coords, local_id);
break;
}
}
}
fn findStartOfTileRow(tile_y: i32) -> u32 {
if config.segments_len == 0u {
return 0u;
}
var end = config.segments_len - 1u;
var start = 0u;
while start < end {
let mid = (start + end) >> 1u;
if pixelSegmentTileY(segments[mid]) < tile_y {
start = mid + 1u;
} else {
end = mid;
}
}
return start;
}
@compute @workgroup_size(16, 4)
fn paint(
@builtin(local_invocation_id) local_id_vec: vec3<u32>,
@builtin(local_invocation_index) local_index: u32,
@builtin(workgroup_id) workgroup_id_vec: vec3<u32>,
) {
let local_id = local_id_vec.xy;
var tile = vec2(-1, i32(workgroup_id_vec.x));
let tile_row_len = (config.width + TILE_WIDTH - 1u) / TILE_WIDTH;
let start_index = findStartOfTileRow(tile.y);
block_index = start_index / BLOCK_LEN;
let _ = loadSegments(tile.y, local_index);
segment_index = start_index & BLOCK_MASK;
var painter: Painter;
painter.queues = Queues(0u, 0u, 0u);
painter.double_area = 0;
painter.cover = 0;
painterNegativeCovers(&painter, tile, local_index, local_id);
painter.cover = 0;
painter.queues.end0 = painter.queues.start1;
tile.x += 1;
while u32(tile.x) <= tile_row_len {
painter.color = clearColor();
let pixel_coords = vec2<i32>(local_id) + tile * vec2(
i32(TILE_WIDTH),
i32(TILE_HEIGHT),
);
painterPaintTile(&painter, tile, local_index, vec2<u32>(pixel_coords), local_id);
textureStore(image, pixel_coords, painter.color);
painter.queues.end0 = painter.queues.start1;
tile.x += 1;
}
}