///|
fn visual_vertices_to_bytes(vertices : Array[VisualVertex]) -> Bytes {
  let out : Array[Byte] = []
  for vertex in vertices {
    push_visual_f32le(out, vertex.x)
    push_visual_f32le(out, vertex.y)
    push_visual_f32le(out, vertex.local_x)
    push_visual_f32le(out, vertex.local_y)
    push_visual_f32le(out, vertex.width)
    push_visual_f32le(out, vertex.height)
    push_visual_f32le(out, vertex.radius)
    push_visual_f32le(out, vertex.mode)
    push_visual_f32le(out, vertex.stroke_width)
    push_visual_f32le(out, vertex.blur_radius)
    push_visual_f32le(out, vertex.start_x)
    push_visual_f32le(out, vertex.start_y)
    push_visual_f32le(out, vertex.end_x)
    push_visual_f32le(out, vertex.end_y)
    push_visual_f32le(out, vertex.r0)
    push_visual_f32le(out, vertex.g0)
    push_visual_f32le(out, vertex.b0)
    push_visual_f32le(out, vertex.a0)
    push_visual_f32le(out, vertex.r1)
    push_visual_f32le(out, vertex.g1)
    push_visual_f32le(out, vertex.b1)
    push_visual_f32le(out, vertex.a1)
  }
  Bytes::from_array(out)
}

///|
fn advanced_vertices_to_bytes(vertices : Array[AdvancedVertex]) -> Bytes {
  let out : Array[Byte] = []
  for vertex in vertices {
    push_visual_f32le(out, vertex.x)
    push_visual_f32le(out, vertex.y)
    push_visual_f32le(out, vertex.u)
    push_visual_f32le(out, vertex.v)
    push_visual_f32le(out, vertex.opacity)
    push_visual_f32le(out, vertex.blend_mode)
    push_visual_f32le(out, vertex.shader_kind)
    push_visual_f32le(out, vertex.effect_amount)
    push_visual_f32le(out, vertex.local_x)
    push_visual_f32le(out, vertex.local_y)
    push_visual_f32le(out, vertex.filter_kind)
    push_visual_f32le(out, vertex.filter_amount)
    push_visual_f32le(out, vertex.r0)
    push_visual_f32le(out, vertex.g0)
    push_visual_f32le(out, vertex.b0)
    push_visual_f32le(out, vertex.a0)
    push_visual_f32le(out, vertex.r1)
    push_visual_f32le(out, vertex.g1)
    push_visual_f32le(out, vertex.b1)
    push_visual_f32le(out, vertex.a1)
    push_advanced_matrix_f32le(out, vertex.matrix)
    push_visual_f32le(out, vertex.rect_width)
    push_visual_f32le(out, vertex.rect_height)
    push_visual_f32le(out, vertex.mask_radius)
    push_visual_f32le(out, vertex.mask_kind)
  }
  Bytes::from_array(out)
}

///|
fn push_advanced_matrix_f32le(
  out : Array[Byte],
  values : Array[Double],
) -> Unit {
  let identity = native_identity_color_matrix()
  for index in 0..<20 {
    match values.get(index) {
      Some(value) => push_visual_f32le(out, value)
      None => push_visual_f32le(out, identity[index])
    }
  }
}

///|
fn push_visual_f32le(out : Array[Byte], value : Double) -> Unit {
  let bits : UInt = Float::from_double(value)
    .reinterpret_as_int()
    .reinterpret_as_uint()
  out.push((bits & 0xFF).to_byte())
  out.push(((bits >> 8) & 0xFF).to_byte())
  out.push(((bits >> 16) & 0xFF).to_byte())
  out.push(((bits >> 24) & 0xFF).to_byte())
}

///|
fn visual_pixel_x_to_ndc(x : Double, width : Double) -> Double {
  if width <= 0.0 {
    -1.0
  } else {
    x / width * 2.0 - 1.0
  }
}

///|
fn visual_pixel_y_to_ndc(y : Double, height : Double) -> Double {
  if height <= 0.0 {
    1.0
  } else {
    1.0 - y / height * 2.0
  }
}