///|
/// SimpleTextBatchBuilder creates draw commands from positioned glyph quads.
///|
pub struct SimpleTextBatchBuilder {
cache : @font.GlyphCache
pipeline_id : Int
}
///|
pub fn SimpleTextBatchBuilder::new(
cache : @font.GlyphCache,
pipeline_id : Int,
) -> SimpleTextBatchBuilder {
{ cache, pipeline_id }
}
// Must mirror `mix_resource_cache_key` in mizchi/gfx/src/contracts.mbt.
// Kept local so the per-glyph vertex hash chain avoids an intermediate
// Array[Int] allocation (text batches can run thousands of vertices).
fn mix_cache_key(seed : Int, value : Int) -> Int {
seed * 16777619 + value + 31
}
// Cheap deterministic 32-bit hash for a Double. resource_cache_key is
// non-functional metadata so any stable mapping works; this avoids the
// int64 round-trip in @gfx.double_to_f32_bits, which dominates on the JS
// target.
fn double_cache_bits(v : Double) -> Int {
(v * 4096.0).to_int()
}
///|
fn simple_text_batch_resource_cache_key(
target : @gfx.ImageHandle,
shader : @gfx.ShaderHandle,
pipeline_id : Int,
region : @gfx.DstRegion,
src_image_ids : Array[Int],
vertex_data : Array[Double],
indices : Array[Int],
) -> Int {
let mut hash = 0x54455854
hash = mix_cache_key(hash, target.id)
hash = mix_cache_key(hash, target.width)
hash = mix_cache_key(hash, target.height)
hash = mix_cache_key(hash, shader.id)
hash = mix_cache_key(hash, pipeline_id)
hash = mix_cache_key(hash, region.x)
hash = mix_cache_key(hash, region.y)
hash = mix_cache_key(hash, region.width)
hash = mix_cache_key(hash, region.height)
hash = mix_cache_key(hash, region.index_count)
hash = mix_cache_key(hash, src_image_ids.length())
hash = mix_cache_key(hash, vertex_data.length())
hash = mix_cache_key(hash, indices.length())
for image_id in src_image_ids {
hash = mix_cache_key(hash, image_id)
}
for v in vertex_data {
hash = mix_cache_key(hash, double_cache_bits(v))
}
for index in indices {
hash = mix_cache_key(hash, index)
}
if hash == 0 {
1
} else {
hash
}
}
///|
pub impl TextBatchBuilder for SimpleTextBatchBuilder with build_draw_commands(
self,
target,
glyphs,
shader,
) {
if glyphs.length() == 0 {
return []
}
let vertex_data : Array[Double] = Array::make(glyphs.length() * 16, 0.0)
let indices : Array[Int] = Array::make(glyphs.length() * 6, 0)
let inv_atlas_w = 1.0 / self.cache.atlas_width
let inv_atlas_h = 1.0 / self.cache.atlas_height
let mut vertex_offset = 0
let mut index_offset = 0
let mut vertex_base = 0
let mut has_region = false
let mut min_x = 0
let mut min_y = 0
let mut max_x = 0
let mut max_y = 0
let mut src_image_id = -1
for glyph in glyphs {
let entry = match self.cache.get(glyph.glyph_id) {
Some(e) => e
None =>
match self.cache.allocate(glyph.glyph_id, glyph.dst_w, glyph.dst_h) {
Some(e) => e
None => continue
}
}
// Compute UV coordinates in atlas
let u0 = entry.atlas_x * inv_atlas_w
let v0 = entry.atlas_y * inv_atlas_h
let u1 = (entry.atlas_x + entry.atlas_w) * inv_atlas_w
let v1 = (entry.atlas_y + entry.atlas_h) * inv_atlas_h
// Quad vertices: position (x, y) + UV (u, v)
let left = glyph.dst_x
let top = glyph.dst_y
let right = glyph.dst_x + glyph.dst_w
let bottom = glyph.dst_y + glyph.dst_h
let left_i = left.to_int()
let top_i = top.to_int()
let right_i = right.to_int()
let bottom_i = bottom.to_int()
if !has_region {
min_x = left_i
min_y = top_i
max_x = right_i
max_y = bottom_i
has_region = true
} else {
if left_i < min_x {
min_x = left_i
}
if top_i < min_y {
min_y = top_i
}
if right_i > max_x {
max_x = right_i
}
if bottom_i > max_y {
max_y = bottom_i
}
}
vertex_data[vertex_offset] = left
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = top
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = u0
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = v0
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = right
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = top
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = u1
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = v0
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = right
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = bottom
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = u1
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = v1
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = left
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = bottom
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = u0
vertex_offset = vertex_offset + 1
vertex_data[vertex_offset] = v1
vertex_offset = vertex_offset + 1
indices[index_offset] = vertex_base
index_offset = index_offset + 1
indices[index_offset] = vertex_base + 1
index_offset = index_offset + 1
indices[index_offset] = vertex_base + 2
index_offset = index_offset + 1
indices[index_offset] = vertex_base + 2
index_offset = index_offset + 1
indices[index_offset] = vertex_base + 3
index_offset = index_offset + 1
indices[index_offset] = vertex_base
index_offset = index_offset + 1
if src_image_id < 0 {
src_image_id = entry.atlas_page_id
}
vertex_base = vertex_base + 4
}
if !has_region {
[]
} else {
let command_vertex_data : Array[Double] = if vertex_offset ==
vertex_data.length() {
vertex_data
} else {
let out : Array[Double] = []
for i in 0..