// 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: UiSpan, y: UiSpan, move_idx: u32, } struct MoveOffset { x: UiSpan, y: UiSpan, parent: u32, } 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; fn scalar_within(s: UiScalar, p: UiSpan) -> UiScalar { return UiScalar( mix(p.start.rel, p.end.rel, s.rel), s.abs + mix(p.start.abs, p.end.abs, 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, entry.x), span_within(r.y, entry.y)); at = entry.parent; } return r; } struct UiSpan { start: UiScalar, end: UiScalar, } struct UiScalar { rel: f32, abs: 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(UiScalar(in.x_start.x, in.x_start.y), UiScalar(in.x_end.x, in.x_end.y)), UiSpan(UiScalar(in.y_start.x, in.y_start.y), UiScalar(in.y_end.x, in.y_end.y)), ); let r = resolve_move(in.move_idx, local); let top_left_rel = vec2(r.x.start.rel, r.y.start.rel); let top_left_abs = vec2(r.x.start.abs, r.y.start.abs); let bot_right_rel = vec2(r.x.end.rel, r.y.end.rel); let bot_right_abs = vec2(r.x.end.abs, r.y.end.abs); let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs); let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs); 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(mask.x, mask.y)); let tl = vec2(m.x.start.rel, m.y.start.rel); let tl_abs = vec2(m.x.start.abs, m.y.start.abs); let br = vec2(m.x.end.rel, m.y.end.rel); let br_abs = vec2(m.x.end.abs, m.y.end.abs); let top_left = floor(tl * window.dim) + floor(tl_abs); let bot_right = floor(br * window.dim) + floor(br_abs); 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; }