///|
/// Command queue + batch-merge layer.
///
/// `CommandQueue` is the trait a renderer enqueues `DrawTrianglesCommand`
/// onto; `SimpleCommandQueue` is the reference implementation that merges
/// adjacent compatible commands within a vertex budget.
///
/// `flush_commands` drives a `GraphicsDriver` from a queue, and
/// `clear_screen` performs the minimum begin/end pair without any draws.
///
/// Ebiten refs:
/// - internal/graphicscommand/commandqueue.go (EnqueueDrawTrianglesCommand, Flush)
///|
/// A renderer-facing buffer of pending `DrawTrianglesCommand`s.
///
/// Renderers `enqueue_draw_triangles` during their `draw` pass and the
/// frame driver later calls `flush` to consume them. The flush phase is
/// where an implementation may merge adjacent compatible commands (same
/// pipeline / shader / blend / images / uniforms) to reduce the number
/// of backend draw calls.
///
/// `SimpleCommandQueue` is the reference implementation; bring your own
/// if you need a different merge policy.
pub(open) trait CommandQueue {
/// Append one command to the back of the queue.
/// Ebiten ref: commandQueue.EnqueueDrawTrianglesCommand.
fn enqueue_draw_triangles(Self, command : DrawTrianglesCommand) -> Unit
/// Drain the queue and return the (possibly merged) commands in
/// submission order. The queue is empty after this call.
/// Ebiten ref: commandQueue.Flush / FlushCommands.
fn flush(Self) -> Array[DrawTrianglesCommand]
}
///|
pub struct SimpleCommandQueue {
mut commands : Array[DrawTrianglesCommand]
} derive(Debug)
///|
fn blend_factor_eq(lhs : BlendFactor, rhs : BlendFactor) -> Bool {
BlendFactor::to_int(lhs) == BlendFactor::to_int(rhs)
}
///|
fn blend_operation_eq(lhs : BlendOperation, rhs : BlendOperation) -> Bool {
BlendOperation::to_int(lhs) == BlendOperation::to_int(rhs)
}
///|
fn blend_equation_eq(lhs : BlendEquation, rhs : BlendEquation) -> Bool {
blend_factor_eq(lhs.src_factor_rgb, rhs.src_factor_rgb) &&
blend_factor_eq(lhs.dst_factor_rgb, rhs.dst_factor_rgb) &&
blend_operation_eq(lhs.op_rgb, rhs.op_rgb) &&
blend_factor_eq(lhs.src_factor_alpha, rhs.src_factor_alpha) &&
blend_factor_eq(lhs.dst_factor_alpha, rhs.dst_factor_alpha) &&
blend_operation_eq(lhs.op_alpha, rhs.op_alpha)
}
///|
fn blend_mode_eq(lhs : BlendMode, rhs : BlendMode) -> Bool {
match lhs {
BlendMode::Copy =>
match rhs {
BlendMode::Copy => true
_ => false
}
BlendMode::Alpha =>
match rhs {
BlendMode::Alpha => true
_ => false
}
BlendMode::Add =>
match rhs {
BlendMode::Add => true
_ => false
}
BlendMode::Multiply =>
match rhs {
BlendMode::Multiply => true
_ => false
}
BlendMode::Custom(eq_l) =>
match rhs {
BlendMode::Custom(eq_r) => blend_equation_eq(eq_l, eq_r)
_ => false
}
}
}
///|
fn int_array_eq(lhs : Array[Int], rhs : Array[Int]) -> Bool {
if lhs.length() != rhs.length() {
return false
}
for i in 0.. Bool {
// Instanced commands cannot be merged
if lhs.instance_count != 1 || rhs.instance_count != 1 {
return false
}
let lhs_explicit = lhs.vertex_data.length() > 0 || lhs.indices.length() > 0
let rhs_explicit = rhs.vertex_data.length() > 0 || rhs.indices.length() > 0
lhs_explicit == rhs_explicit &&
lhs.vertex_data.length() + rhs.vertex_data.length() <= max_merge_vertex_floats &&
lhs.dst.id == rhs.dst.id &&
lhs.shader.id == rhs.shader.id &&
blend_mode_eq(lhs.blend, rhs.blend) &&
lhs.pipeline_id == rhs.pipeline_id &&
lhs.uniform_hash == rhs.uniform_hash &&
lhs.index_offset == rhs.index_offset &&
int_array_eq(lhs.src_image_ids, rhs.src_image_ids) &&
int_array_eq(lhs.uniform_dwords, rhs.uniform_dwords)
}
///|
fn can_merge_draw_triangles_with_vertex_budget(
lhs : DrawTrianglesCommand,
rhs : DrawTrianglesCommand,
lhs_vertex_float_count : Int,
) -> Bool {
can_merge_draw_triangles(lhs, rhs) &&
lhs_vertex_float_count + rhs.vertex_data.length() <= max_merge_vertex_floats
}
///|
fn merge_draw_triangles_range(
commands : Array[DrawTrianglesCommand],
start : Int,
end : Int,
) -> DrawTrianglesCommand {
let base = commands[start]
let explicit = base.vertex_data.length() > 0 || base.indices.length() > 0
let merged_regions : Array[DstRegion] = []
for i in start.. SimpleCommandQueue {
{ commands: [] }
}
///|
pub fn[T : GraphicsDriver, Q : CommandQueue] flush_commands(
driver : T,
queue : Q,
present : Bool,
clear_color? : Color,
) -> Unit raise {
let commands = queue.flush()
if commands.length() == 0 && !present {
()
} else {
let color = match clear_color {
Some(c) => c
None => { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
}
driver.begin({ clear_color: color, clear_enabled: true, present })
for command in commands {
driver.draw_triangles(command)
}
driver.end(present)
}
}
///|
pub fn[T : GraphicsDriver] clear_screen(
driver : T,
color : Color,
) -> Unit raise {
driver.begin({ clear_color: color, clear_enabled: true, present: false })
driver.end(false)
}