bevy-react 0.4.0

Drive bevy_ui from a React app over an embedded V8 runtime.
Documentation
// Composite quad for a captured UI layer (see `layer/render.rs`).
//
// The capture texture holds PREMULTIPLIED color: straight-alpha blending onto
// a transparent-black target accumulates `rgb * a` in the color channels. The
// group alpha therefore multiplies rgb AND a, and the pipeline blends with
// (One, OneMinusSrcAlpha) — premultiplied "over".
//
// Group 2 carries the per-quad composite params (`transform3d` support): a
// screen-space model matrix and a screen-space clip rect. Untransformed quads
// ride the same path with an identity matrix and an open clip sentinel (their
// ancestor clip was already clamped CPU-side by `clip_quad`); transformed
// quads keep full geometry and clip here instead — an axis-aligned rect can't
// clamp a rotated quad's vertices.

#import bevy_render::view::View

@group(0) @binding(0) var<uniform> view: View;
@group(1) @binding(0) var atlas_texture: texture_2d<f32>;
@group(1) @binding(1) var atlas_sampler: sampler;

// Mirrored byte-for-byte by `render/transform3d.rs::CompositeUniforms` (128
// bytes, guarded by `composite_uniforms_match_the_documented_wgsl_layout`).
// Offsets: model @0, clip_min @64, clip_max @72, edge_feather @80, radius
// @96, box_center @112, box_size @120. Pad names are digit-free (naga's
// namer appends `_` to digit-suffixed identifiers).
struct CompositeParams {
    model: mat4x4<f32>,
    clip_min: vec2<f32>,
    clip_max: vec2<f32>,
    edge_feather: f32,
    pad_a: f32,
    pad_b: vec2<f32>,
    // Rounded-corner mask: per-corner radii [TL, TR, BR, BL] (physical px,
    // the node's layout-resolved values) over the UNCLIPPED border box.
    // All-zero disables the mask (the edge_feather pattern); today only
    // backdrop quads set it — frost clipped to the rounded panel.
    radius: vec4<f32>,
    box_center: vec2<f32>,
    box_size: vec2<f32>,
}

@group(2) @binding(0) var<uniform> params: CompositeParams;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
    @location(1) alpha: f32,
    // Homogeneous screen-space position (pre-divide) for the fragment clip
    // test: dividing the perspective-correct-interpolated pair per fragment
    // recovers the true screen position in `clip_min/max`'s space, independent
    // of the render target (screen or a nested layer's capture texture).
    @location(2) screen_pos: vec2<f32>,
    @location(3) screen_w: f32,
}

// Signed distance from `point` (measured from the box CENTER) to a rounded
// box of `size` with per-quadrant `corner_radii` [TL, TR, BR, BL]; negative
// inside. Ported verbatim from bevy_ui_render's `ui.wgsl::sd_rounded_box`
// (including the .xy/.wz quadrant-select swap) so the frost's corner math is
// bit-identical to the one bevy_ui paints the node's own background with.
fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) -> f32 {
    // If 0.0 < y then select bottom left (w) and bottom right corner radius (z).
    // Else select top left (x) and top right corner radius (y).
    let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);
    // w and z are swapped above so that both pairs are in left-to-right order, otherwise this second
    // select statement would return the incorrect value for the bottom pair.
    let radius = select(rs.x, rs.y, 0.0 < point.x);
    // Vector from the corner closest to the point, to the point.
    let corner_to_point = abs(point) - 0.5 * size;
    // Vector from the center of the radius circle to the point.
    let q = corner_to_point + radius;
    // Length from center of the radius circle to the point, zeros a component if the point is not
    // within the quadrant of the radius circle that is part of the curved corner.
    let l = length(max(q, vec2(0.0)));
    let m = min(max(q.x, q.y), 0.0);
    return l + m - radius;
}

@vertex
fn vertex(
    @location(0) position: vec3<f32>,
    @location(1) uv: vec2<f32>,
    @location(2) alpha: f32,
) -> VertexOutput {
    var out: VertexOutput;
    // Positions are physical screen px; the model matrix is the layer's 3D
    // transform in that same space (identity when untransformed). `w` is kept
    // REAL through the projection — that is what buys perspective-correct UV
    // interpolation — but `z` is flattened post-transform: the phase's view
    // (the stock UI view, or an outer layer's capture view) projects with a
    // near plane at the UI plane, and a rotated quad's depth excursions would
    // otherwise be depth-clipped. Flattening is exactly the CSS projective
    // flatten — the homography lives entirely in xy/w.
    let world = params.model * vec4(position, 1.0);
    out.position = view.clip_from_world * vec4(world.xy, 0.0, world.w);
    out.uv = uv;
    out.alpha = alpha;
    out.screen_pos = world.xy;
    out.screen_w = world.w;
    return out;
}

@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
    // Analytic edge AA for transformed quads: their diagonal silhouettes
    // rasterize without MSAA, so coverage feathers over ~edge_feather px
    // centered on the TRUE rect edge (uv 0/1 — the quad geometry is inflated
    // by the same width, providing the outside half; see
    // `inflated_transform_quad`). Derivatives convert uv distance to screen
    // px per axis BEFORE the min — correct under anisotropic compression —
    // and must be computed before the clip discard (uniform control flow).
    // `edge_feather == 0` hard-disables the term: untransformed quads are
    // CPU-clamped (uv_min/max inside [0,1] at clipped edges, where this
    // distance would feather wrongly) and must stay pixel-identical.
    let dist = min(in.uv, vec2(1.0) - in.uv);
    let dist_px = dist / max(fwidth(in.uv), vec2(1e-6));
    let edge_px = min(dist_px.x, dist_px.y);
    let feathered = clamp(0.5 + edge_px / max(params.edge_feather, 1e-6), 0.0, 1.0);
    var coverage = select(1.0, feathered, params.edge_feather > 0.0);

    // True screen position (perspective divide) — shared by the rounded mask
    // and the ancestor-clip test below.
    let screen = in.screen_pos / in.screen_w;

    // Rounded-corner mask (backdrop quads): clip coverage to the node's
    // rounded border box, antialiased with bevy_ui's own convention —
    // `saturate(0.5 - sd)` on the raw physical-px distance (ui.wgsl's
    // `antialias`; deliberately no fwidth) — so the frost edge coincides
    // with the node's painted rounded background. All-zero radii disable the
    // term exactly (content quads, square backdrops stay pixel-identical).
    let sd = sd_rounded_box(screen - params.box_center, params.box_size, params.radius);
    let rounded = saturate(0.5 - sd);
    coverage *= select(1.0, rounded, any(params.radius > vec4(0.0)));

    // Ancestor clip of a transformed quad (screen-space rect vs. the true
    // screen position) — a HARD cut, like every other overflow edge.
    // Open-sentinel bounds make this a no-op for CPU-clamped/unclipped quads.
    if any(screen < params.clip_min) || any(screen > params.clip_max) {
        discard;
    }
    // Premultiplied output: coverage multiplies rgb AND a, like the group alpha.
    return textureSample(atlas_texture, atlas_sampler, in.uv) * in.alpha * coverage;
}