///|
fn pixel_to_ndc_vertices(
vertices : Array[Double],
screen_w : Double,
screen_h : Double,
) -> Array[Double] {
let out : Array[Double] = []
let mut i = 0
while i < vertices.length() {
let px = vertices[i]
let py = vertices[i + 1]
let u = vertices[i + 2]
let v = vertices[i + 3]
out.push(px / screen_w * 2.0 - 1.0)
out.push(1.0 - py / screen_h * 2.0)
out.push(u)
out.push(v)
i = i + 4
}
out
}
///|
fn new_primitive_command(
frame : RenderFrame2D,
vertex_data : Array[Double],
indices : Array[Int],
color : @gfx.Color,
) -> @gfx.DrawTrianglesCommand {
@gfx.new_draw_triangles_command(
frame.dst,
frame.shader,
[
@gfx.new_dst_region(
0,
0,
frame.screen_w,
frame.screen_h,
indices.length(),
),
],
0,
frame.pipeline_id,
frame.uniform_hash,
frame.blend,
vertex_data,
indices,
[],
color_to_uniform_dwords(color),
)
}
///|
pub fn append_primitive_batch(
commands : Array[@gfx.DrawTrianglesCommand],
frame : RenderFrame2D,
vertex_data : Array[Double],
indices : Array[Int],
color : @gfx.Color,
) -> Unit {
commands.push(new_primitive_command(frame, vertex_data, indices, color))
}
///|
pub(all) struct RectFill2D {
x : Double
y : Double
w : Double
h : Double
color : @gfx.Color
}
///|
pub fn RectFill2D::new(
x~ : Double,
y~ : Double,
w~ : Double,
h~ : Double,
color~ : @gfx.Color,
) -> RectFill2D {
{ x, y, w, h, color, }
}
///|
/// Push one axis-aligned quad's NDC geometry onto `vertices` / `indices`.
///
/// The only place the pixel-to-NDC arithmetic for a filled rect lives on the
/// appending path, so the single-rect and batched callers cannot drift apart.
/// Takes the screen size and the rect as scalars rather than a `RenderFrame2D`
/// and a `RectFill2D` because a batched run calls it once per quad: the frame
/// conversion hoists out of the loop and no `RectFill2D` is allocated per quad.
fn push_rect_fill_geometry(
vertices : Array[Double],
indices : Array[Int],
screen_w : Double,
screen_h : Double,
x : Double,
y : Double,
w : Double,
h : Double,
) -> Unit {
// Same arithmetic as `ndc_rect_fill_vertices`, written straight into the
// caller's arrays. Going through that helper allocated a tuple and two
// arrays per quad and then copied all 22 elements back out, which is most of
// what a batched text run used to pay per dot.
let x0 = x / screen_w * 2.0 - 1.0
let y0 = 1.0 - y / screen_h * 2.0
let x1 = (x + w) / screen_w * 2.0 - 1.0
let y1 = 1.0 - (y + h) / screen_h * 2.0
let base_vertex = vertices.length() / 4
vertices.push(x0)
vertices.push(y0)
vertices.push(0.0)
vertices.push(0.0)
vertices.push(x1)
vertices.push(y0)
vertices.push(1.0)
vertices.push(0.0)
vertices.push(x1)
vertices.push(y1)
vertices.push(1.0)
vertices.push(1.0)
vertices.push(x0)
vertices.push(y1)
vertices.push(0.0)
vertices.push(1.0)
indices.push(base_vertex)
indices.push(base_vertex + 1)
indices.push(base_vertex + 2)
indices.push(base_vertex + 2)
indices.push(base_vertex + 3)
indices.push(base_vertex)
}
///|
pub fn append_rect_fill_geometry(
vertices : Array[Double],
indices : Array[Int],
frame : RenderFrame2D,
rect : RectFill2D,
) -> Unit {
push_rect_fill_geometry(
vertices,
indices,
frame.screen_w.to_double(),
frame.screen_h.to_double(),
rect.x,
rect.y,
rect.w,
rect.h,
)
}
///|
pub fn append_rect_fill(
commands : Array[@gfx.DrawTrianglesCommand],
frame : RenderFrame2D,
rect : RectFill2D,
) -> Unit {
let vertices : Array[Double] = []
let indices : Array[Int] = []
append_rect_fill_geometry(vertices, indices, frame, rect)
append_primitive_batch(commands, frame, vertices, indices, rect.color)
}
///|
pub(all) struct Line2D {
x0 : Double
y0 : Double
x1 : Double
y1 : Double
width : Double
color : @gfx.Color
}
///|
pub fn Line2D::new(
x0~ : Double,
y0~ : Double,
x1~ : Double,
y1~ : Double,
width? : Double = 1.0,
color~ : @gfx.Color,
) -> Line2D {
{ x0, y0, x1, y1, width, color, }
}
///|
pub fn append_line(
commands : Array[@gfx.DrawTrianglesCommand],
frame : RenderFrame2D,
line : Line2D,
) -> Unit {
let (vertices, indices) = line_vertices(
line.x0,
line.y0,
line.x1,
line.y1,
line.width,
)
commands.push(
new_primitive_command(
frame,
pixel_to_ndc_vertices(
vertices,
frame.screen_w.to_double(),
frame.screen_h.to_double(),
),
indices,
line.color,
),
)
}
///|
pub(all) struct RectOutline2D {
x : Double
y : Double
w : Double
h : Double
line_width : Double
color : @gfx.Color
}
///|
pub fn RectOutline2D::new(
x~ : Double,
y~ : Double,
w~ : Double,
h~ : Double,
line_width? : Double = 1.0,
color~ : @gfx.Color,
) -> RectOutline2D {
{ x, y, w, h, line_width, color, }
}
///|
pub fn append_rect_outline(
commands : Array[@gfx.DrawTrianglesCommand],
frame : RenderFrame2D,
rect : RectOutline2D,
) -> Unit {
let (vertices, indices) = rect_outline_vertices(
rect.x,
rect.y,
rect.w,
rect.h,
rect.line_width,
)
commands.push(
new_primitive_command(
frame,
pixel_to_ndc_vertices(
vertices,
frame.screen_w.to_double(),
frame.screen_h.to_double(),
),
indices,
rect.color,
),
)
}