///|
/// 2D draw-command builders.
///
/// Ebiten refs:
/// - internal/atlas/image.go (atlas page + region UV)
/// - internal/graphicscommand/command.go (DrawTriangles payload)
// Shared, read-only quad-index pattern. The downstream gfx queue / merger
// only reads `.length()` on indices, so reusing one Array per call saves an
// allocation on every sprite draw. Do not mutate.
let atlas_quad_indices_shared : Array[Int] = [0, 1, 2, 2, 3, 0]
///|
pub fn atlas_quad_indices() -> Array[Int] {
atlas_quad_indices_shared
}
///|
pub fn atlas_quad_vertex_data(
source : @atlas.AtlasDrawSource,
left : Double,
top : Double,
right : Double,
bottom : Double,
) -> Array[Double] {
[
left,
top,
source.u0,
source.v0,
right,
top,
source.u1,
source.v0,
right,
bottom,
source.u1,
source.v1,
left,
bottom,
source.u0,
source.v1,
]
}
// Must mirror `mix_resource_cache_key` in mizchi/gfx/src/contracts.mbt.
// Kept local so we can hash inline without allocating a values array.
fn mix_cache_key(seed : Int, value : Int) -> Int {
seed * 16777619 + value + 31
}
// Cheap 32-bit hash for a Double. Used only for the (non-functional)
// resource_cache_key metadata, so any deterministic mapping works -
// gfx's queue propagates this value but never compares it. Scaling by 4096
// keeps sub-quarter-pixel resolution which is finer than sprite cache_key
// needs across the typical screen-coordinate range.
fn double_cache_bits(v : Double) -> Int {
(v * 4096.0).to_int()
}
///|
fn atlas_quad_resource_cache_key(
dst : @gfx.ImageHandle,
shader : @gfx.ShaderHandle,
dst_region : @gfx.DstRegion,
blend : @gfx.BlendMode,
source : @atlas.AtlasDrawSource,
left : Double,
top : Double,
right : Double,
bottom : Double,
uniform_dwords : Array[Int],
) -> Int {
let mut hash = 0x41544C53
hash = mix_cache_key(hash, dst.id)
hash = mix_cache_key(hash, dst.width)
hash = mix_cache_key(hash, dst.height)
hash = mix_cache_key(hash, shader.id)
hash = mix_cache_key(hash, dst_region.x)
hash = mix_cache_key(hash, dst_region.y)
hash = mix_cache_key(hash, dst_region.width)
hash = mix_cache_key(hash, dst_region.height)
hash = mix_cache_key(hash, dst_region.index_count)
hash = mix_cache_key(hash, @gfx.BlendMode::to_int(blend))
hash = mix_cache_key(hash, source.page_image_id)
hash = mix_cache_key(hash, double_cache_bits(source.u0))
hash = mix_cache_key(hash, double_cache_bits(source.v0))
hash = mix_cache_key(hash, double_cache_bits(source.u1))
hash = mix_cache_key(hash, double_cache_bits(source.v1))
hash = mix_cache_key(hash, double_cache_bits(left))
hash = mix_cache_key(hash, double_cache_bits(top))
hash = mix_cache_key(hash, double_cache_bits(right))
hash = mix_cache_key(hash, double_cache_bits(bottom))
hash = mix_cache_key(hash, uniform_dwords.length())
for dword in uniform_dwords {
hash = mix_cache_key(hash, dword)
}
if hash == 0 {
1
} else {
hash
}
}
///|
pub fn new_atlas_quad_draw_command(
dst : @gfx.ImageHandle,
shader : @gfx.ShaderHandle,
dst_region : @gfx.DstRegion,
index_offset : Int,
pipeline_id : Int,
uniform_hash : Int,
blend : @gfx.BlendMode,
source : @atlas.AtlasDrawSource,
left : Double,
top : Double,
right : Double,
bottom : Double,
uniform_dwords : Array[Int],
) -> @gfx.DrawTrianglesCommand {
@gfx.new_draw_triangles_command(
dst,
shader,
[dst_region],
index_offset,
pipeline_id,
uniform_hash,
blend,
atlas_quad_vertex_data(source, left, top, right, bottom),
atlas_quad_indices(),
[source.page_image_id],
uniform_dwords,
resource_cache_key=atlas_quad_resource_cache_key(
dst, shader, dst_region, blend, source, left, top, right, bottom, uniform_dwords,
),
)
}
///|
/// Batched atlas quad cache key. Hashes the shared state (dst, shader,
/// blend, source, region) plus a small fingerprint of the rect buffer
/// (count + first/last endpoints) rather than every rect element. The
/// cache key is non-functional metadata (gfx propagates it but never
/// compares it), so a coarse fingerprint is sufficient and avoids
/// O(N) hash work per batch.
fn atlas_quad_batch_resource_cache_key(
dst : @gfx.ImageHandle,
shader : @gfx.ShaderHandle,
dst_region : @gfx.DstRegion,
blend : @gfx.BlendMode,
source : @atlas.AtlasDrawSource,
rects : Array[Double],
uniform_dwords : Array[Int],
) -> Int {
let mut hash = 0x41514254 // "AQBT"
hash = mix_cache_key(hash, dst.id)
hash = mix_cache_key(hash, dst.width)
hash = mix_cache_key(hash, dst.height)
hash = mix_cache_key(hash, shader.id)
hash = mix_cache_key(hash, dst_region.x)
hash = mix_cache_key(hash, dst_region.y)
hash = mix_cache_key(hash, dst_region.width)
hash = mix_cache_key(hash, dst_region.height)
hash = mix_cache_key(hash, dst_region.index_count)
hash = mix_cache_key(hash, @gfx.BlendMode::to_int(blend))
hash = mix_cache_key(hash, source.page_image_id)
hash = mix_cache_key(hash, double_cache_bits(source.u0))
hash = mix_cache_key(hash, double_cache_bits(source.v0))
hash = mix_cache_key(hash, double_cache_bits(source.u1))
hash = mix_cache_key(hash, double_cache_bits(source.v1))
hash = mix_cache_key(hash, rects.length())
hash = mix_cache_key(hash, uniform_dwords.length())
// Sample endpoints rather than the whole buffer (O(1) instead of O(N)).
if rects.length() >= 4 {
hash = mix_cache_key(hash, double_cache_bits(rects[0]))
hash = mix_cache_key(hash, double_cache_bits(rects[1]))
hash = mix_cache_key(hash, double_cache_bits(rects[2]))
hash = mix_cache_key(hash, double_cache_bits(rects[3]))
let last = rects.length() - 4
hash = mix_cache_key(hash, double_cache_bits(rects[last]))
hash = mix_cache_key(hash, double_cache_bits(rects[last + 1]))
hash = mix_cache_key(hash, double_cache_bits(rects[last + 2]))
hash = mix_cache_key(hash, double_cache_bits(rects[last + 3]))
}
for dword in uniform_dwords {
hash = mix_cache_key(hash, dword)
}
if hash == 0 {
1
} else {
hash
}
}
///|
/// Build a single DrawTrianglesCommand covering N quads that share one
/// atlas source, shader, dst region, pipeline, blend, and uniform state.
/// `rects` is laid out as [l0, t0, r0, b0, l1, t1, r1, b1, ...] so the
/// caller never allocates per-quad arrays. Quad count = rects.length() / 4;
/// trailing partial elements are ignored. dst_region.index_count is
/// overridden to 6*N so the GPU draws all quads in one call.
pub fn new_atlas_quad_batch_draw_command(
dst : @gfx.ImageHandle,
shader : @gfx.ShaderHandle,
dst_region : @gfx.DstRegion,
pipeline_id : Int,
uniform_hash : Int,
blend : @gfx.BlendMode,
source : @atlas.AtlasDrawSource,
rects : Array[Double],
uniform_dwords : Array[Int],
) -> @gfx.DrawTrianglesCommand {
let quad_count = rects.length() / 4
let vertex_data : Array[Double] = Array::make(quad_count * 16, 0.0)
let indices : Array[Int] = Array::make(quad_count * 6, 0)
let u0 = source.u0
let v0 = source.v0
let u1 = source.u1
let v1 = source.v1
for i in 0..