// Prepended to every primitive's shader, which declares its own instance data // as `var : array` at group 1 binding 0, and an `fs_main` // shading one instance of it. What it samples, if anything, is bound at group // 2: the texture at binding 0 and the sampler at binding 1. @group(0) @binding(0) var window: WindowUniform; @group(0) @binding(1) var masks: array; @group(0) @binding(2) var move_offsets: array; struct WindowUniform { dim: vec2, }; struct Mask { x: RawSpan, y: RawSpan, move_idx: u32, } struct MoveOffset { x: RawSpan, y: RawSpan, parent: u32, } // `PX_STEP` and `REL_STEP` are prepended from `iris_core`'s own constants: // what it stores is a whole count of each, both powers of two, so decoding // is exact and the number here is the number the CPU decided. // Every coordinate the CPU decided is a whole count of `PX_STEP`, so one that // composes to within half a step of a pixel boundary is on that boundary and // belongs to the pixel above it. Flooring the product instead drops a pixel // wherever a fraction divides a window exactly: a fifth of 1920 comes out of // `REL_STEP` as 383.99998, and five tabs each lose their last column. fn snap_floor(v: vec2) -> vec2 { return floor(v + PX_STEP * 0.5); } struct RawScalar { rel: i32, px: i32, } struct RawSpan { start: RawScalar, end: RawScalar, } fn scalar_of(raw: RawScalar) -> UiScalar { return UiScalar(f32(raw.rel) * REL_STEP, f32(raw.px) * PX_STEP); } fn span_of(raw: RawSpan) -> UiSpan { return UiSpan(scalar_of(raw.start), scalar_of(raw.end)); } fn scalar_of_pair(raw: vec2) -> UiScalar { return UiScalar(f32(raw.x) * REL_STEP, f32(raw.y) * PX_STEP); } struct Region { x: UiSpan, y: UiSpan, } const MOVE_NONE: u32 = 4294967295u; // Keep in step with `iris_core::CHAIN_LIMIT`. It bounds a malformed cycle // rather than any real tree, and the CPU walk uses the same number so both // resolve a deep one the same way. const CHAIN_LIMIT: u32 = 64u; // The same expression `UiScalar::within` uses, in floats rather than on the // CPU's grid: a move is resolved here so that scrolling a subtree writes one // entry instead of walking it. What has to hold is that this agrees with // itself frame to frame, not that it matches the CPU to the last bit. fn scalar_within(s: UiScalar, p: UiSpan) -> UiScalar { return UiScalar( p.start.rel + (p.end.rel - p.start.rel) * s.rel, s.px + (p.start.px + (p.end.px - p.start.px) * s.rel), ); } fn span_within(s: UiSpan, p: UiSpan) -> UiSpan { return UiSpan(scalar_within(s.start, p), scalar_within(s.end, p)); } fn resolve_move(idx: u32, local: Region) -> Region { var r = local; var at = idx; for (var step = 0u; step < CHAIN_LIMIT; step++) { if at == MOVE_NONE { break; } let entry = move_offsets[at]; r = Region(span_within(r.x, span_of(entry.x)), span_within(r.y, span_of(entry.y))); at = entry.parent; } return r; } struct UiSpan { start: UiScalar, end: UiScalar, } struct UiScalar { rel: f32, px: f32, } struct InstanceInput { @location(0) x_start: vec2, @location(1) x_end: vec2, @location(2) y_start: vec2, @location(3) y_end: vec2, @location(4) mask_idx: u32, @location(5) move_idx: u32, } struct VertexOutput { @location(0) top_left: vec2, @location(1) bot_right: vec2, @location(2) uv: vec2, @location(3) @interpolate(flat) mask_idx: u32, @location(4) @interpolate(flat) idx: u32, @builtin(position) clip_position: vec4, }; @vertex fn vs_main( @builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32, in: InstanceInput, ) -> VertexOutput { var out: VertexOutput; let local = Region( UiSpan(scalar_of_pair(in.x_start), scalar_of_pair(in.x_end)), UiSpan(scalar_of_pair(in.y_start), scalar_of_pair(in.y_end)), ); let r = resolve_move(in.move_idx, local); let top_left_rel = vec2(r.x.start.rel, r.y.start.rel); let top_left_px = vec2(r.x.start.px, r.y.start.px); let bot_right_rel = vec2(r.x.end.rel, r.y.end.rel); let bot_right_px = vec2(r.x.end.px, r.y.end.px); let top_left = snap_floor(top_left_rel * window.dim) + snap_floor(top_left_px); let bot_right = snap_floor(bot_right_rel * window.dim) + snap_floor(bot_right_px); let size = bot_right - top_left; let uv = vec2( f32(vi % 2u), f32(vi / 2u) ); let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0; out.clip_position = vec4(pos.x, -pos.y, 0.0, 1.0); out.uv = uv; out.top_left = top_left; out.bot_right = bot_right; out.mask_idx = in.mask_idx; out.idx = ii; return out; } fn masked(in: VertexOutput, color: vec4) -> vec4 { if in.mask_idx == 4294967295u { return color; } let mask = masks[in.mask_idx]; // Its own chain, not the drawn primitive's, so a stationary viewport // clips content that moves inside it. let m = resolve_move(mask.move_idx, Region(span_of(mask.x), span_of(mask.y))); let tl = vec2(m.x.start.rel, m.y.start.rel); let tl_px = vec2(m.x.start.px, m.y.start.px); let br = vec2(m.x.end.rel, m.y.end.rel); let br_px = vec2(m.x.end.px, m.y.end.px); let top_left = snap_floor(tl * window.dim) + snap_floor(tl_px); let bot_right = snap_floor(br * window.dim) + snap_floor(br_px); let pos = in.clip_position.xy; if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y { return color * 0.0; } return color; }