///|
/// Core command-buffer types: `DstRegion`, `Color`, `RenderPassDesc`,
/// `DrawTrianglesCommand`, `DrawCommandDispatch` and the
/// `build_draw_command_dispatch` / `dispatch_checksum` helpers.
///
/// These are deliberately type-erased (vertex payload is
/// `Array[Double]`, indices are `Array[Int]`, uniforms are dwords) so
/// the same command struct flows through any `GraphicsDriver` impl.
///
/// Ebiten refs:
/// - internal/graphicsdriver/graphics.go
/// - internal/graphicscommand/commandqueue.go
/// - internal/atlas/image.go
///|
/// One destination scissor rectangle on the target image, plus the
/// number of indices the caller intends to consume inside it. A
/// command can have several `DstRegion`s when batching independent
/// scissors that share state.
pub(all) struct DstRegion {
x : Int
y : Int
width : Int
height : Int
index_count : Int
} derive(Debug)
///|
pub impl Show for DstRegion with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
pub fn new_dst_region(
x : Int,
y : Int,
width : Int,
height : Int,
index_count : Int,
) -> DstRegion {
{ x, y, width, height, index_count }
}
///|
/// RGBA color with channels in `[0.0, 1.0]` (linear, premultiplied is
/// left to the shader). Values outside the range are not clamped here.
pub(all) struct Color {
r : Double
g : Double
b : Double
a : Double
} derive(Debug)
///|
pub impl Show for Color with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
/// What `GraphicsDriver.begin` does before any draw calls of a pass.
///
/// `clear_enabled=false` keeps the previous framebuffer contents
/// (useful for additive overlays). `present=true` swaps buffers at
/// `end`; set false for offscreen render-to-texture passes.
pub(all) struct RenderPassDesc {
clear_color : Color
clear_enabled : Bool
present : Bool
} derive(Debug)
///|
pub impl Show for RenderPassDesc with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
pub fn new_color(r : Double, g : Double, b : Double, a : Double) -> Color {
{ r, g, b, a }
}
///|
pub fn new_render_pass_desc(
clear_color : Color,
clear_enabled : Bool,
present? : Bool = true,
) -> RenderPassDesc {
{ clear_color, clear_enabled, present }
}
///|
/// One indexed-triangle draw issued against a render target.
///
/// This is the unit of work both the `CommandQueue` and the
/// `GraphicsDriver` traffic in. Two adjacent commands with the same
/// `dst` / `shader` / `pipeline_id` / `uniform_hash` / `blend` /
/// `src_image_ids` / `uniform_dwords` and equal `index_offset` can be
/// merged into one larger draw by the queue.
///
/// Vertex / index data is type-erased into flat arrays so the command
/// stays backend-neutral; the shader interprets `vertex_data` according
/// to its declared input layout. `vertex_stride_hint` is a hint to the
/// backend pipeline picker (0 = auto, 4 = 2D, 8 = 3D, 16 = skinned).
pub(all) struct DrawTrianglesCommand {
/// The image being drawn onto.
dst : ImageHandle
/// The compiled shader to execute.
shader : ShaderHandle
/// Destination scissor / region rectangles. Each region accumulates
/// the triangle count it covers.
dst_regions : Array[DstRegion]
/// Offset into the index buffer when the backend caches geometry by
/// `resource_cache_key`. 0 for explicit payloads.
index_offset : Int
/// Backend pipeline identifier (opaque, hashed by the queue).
pipeline_id : Int
/// Hash of the canonical uniform payload (`uniform_dwords`).
uniform_hash : Int
/// Blend state. `Custom(BlendEquation)` for non-preset modes.
blend : BlendMode
/// Optional explicit vertex payload. Empty when the backend reuses
/// geometry via `resource_cache_key`.
/// Ebiten ref: internal/graphicscommand/command.go (vertex payload).
vertex_data : Array[Double]
/// Optional explicit index payload. Empty when the backend resolves
/// indices from cached geometry.
indices : Array[Int]
/// Bound source-image ids (texture0, texture1, ...).
src_image_ids : Array[Int]
/// Canonicalized uniform payload as raw dwords.
uniform_dwords : Array[Int]
/// 1 for ordinary draws; > 1 enables instanced rendering and
/// disables batch-merge.
instance_count : Int
/// 0 to force the explicit payload to be uploaded; non-zero lets the
/// backend reuse a previously uploaded buffer with the same key.
resource_cache_key : Int
/// 0 to let the backend pick the stride from the shader; > 0 sets it
/// explicitly (4 = 2D, 8 = 3D, 16 = skinned).
vertex_stride_hint : Int
} derive(Debug)
///|
pub fn new_draw_triangles_command(
dst : ImageHandle,
shader : ShaderHandle,
dst_regions : Array[DstRegion],
index_offset : Int,
pipeline_id : Int,
uniform_hash : Int,
blend : BlendMode,
vertex_data : Array[Double],
indices : Array[Int],
src_image_ids : Array[Int],
uniform_dwords : Array[Int],
instance_count? : Int = 1,
resource_cache_key? : Int = 0,
vertex_stride_hint? : Int = 0,
) -> DrawTrianglesCommand {
{
dst,
shader,
dst_regions,
index_offset,
pipeline_id,
uniform_hash,
blend,
vertex_data,
indices,
src_image_ids,
uniform_dwords,
instance_count,
resource_cache_key,
vertex_stride_hint,
}
}
///|
fn mix_resource_cache_key(seed : Int, value : Int) -> Int {
seed * 16777619 + value + 31
}
///|
pub fn build_resource_cache_key(seed : Int, values : Array[Int]) -> Int {
let mut hash = if seed == 0 { 146959810 } else { seed }
for value in values {
hash = mix_resource_cache_key(hash, value)
}
if hash == 0 {
1
} else {
hash
}
}
///|
pub(all) struct DrawCommandDispatch {
draw_calls : Int
pipeline_id : Int
uniform_hash : Int
blend_mode : Int
dst_image_id : Int
shader_id : Int
index_offset : Int
region_count : Int
total_index_count : Int
vertex_float_count : Int
index_count : Int
src_image_count : Int
uniform_dword_count : Int
} derive(Debug)
///|
pub impl Show for DrawCommandDispatch with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
fn normalized_region_triangle_count(region : DstRegion) -> Int {
if region.index_count <= 0 {
1
} else {
let triangles = region.index_count / 3
if triangles <= 0 {
1
} else {
triangles
}
}
}
///|
fn normalized_region_index_count(region : DstRegion) -> Int {
if region.index_count <= 0 {
3
} else {
region.index_count
}
}
///|
pub fn DrawTrianglesCommand::estimated_draw_call_count(
self : DrawTrianglesCommand,
) -> Int {
let mut draw_calls = 0
for region in self.dst_regions {
draw_calls = draw_calls + normalized_region_triangle_count(region)
}
draw_calls
}
///|
pub fn DrawTrianglesCommand::estimated_total_index_count(
self : DrawTrianglesCommand,
) -> Int {
let mut total = 0
for region in self.dst_regions {
total = total + normalized_region_index_count(region)
}
total
}
///|
fn resolved_index_count(command : DrawTrianglesCommand) -> Int {
let explicit = command.indices.length()
if explicit > 0 {
explicit
} else {
command.estimated_total_index_count()
}
}
///|
/// Build the `DrawCommandDispatch` summary the backend / queue use for
/// fingerprinting and merge decisions.
pub fn DrawTrianglesCommand::build_dispatch(
self : DrawTrianglesCommand,
) -> DrawCommandDispatch {
{
draw_calls: self.estimated_draw_call_count(),
pipeline_id: self.pipeline_id,
uniform_hash: self.uniform_hash,
blend_mode: self.blend.to_int(),
dst_image_id: self.dst.id,
shader_id: self.shader.id,
index_offset: self.index_offset,
region_count: self.dst_regions.length(),
total_index_count: self.estimated_total_index_count(),
vertex_float_count: self.vertex_data.length(),
index_count: resolved_index_count(self),
src_image_count: self.src_image_ids.length(),
uniform_dword_count: self.uniform_dwords.length(),
}
}
///|
/// Sum of every Int field of a `DrawCommandDispatch`. Cheap, branch-free
/// fingerprint useful in benches and tests where the caller just needs a
/// `b.keep`-able value derived from the whole dispatch (it is not a
/// cryptographic hash and is not collision-free).
pub fn DrawCommandDispatch::checksum(self : DrawCommandDispatch) -> Int {
self.draw_calls +
self.pipeline_id +
self.uniform_hash +
self.blend_mode +
self.dst_image_id +
self.shader_id +
self.index_offset +
self.region_count +
self.total_index_count +
self.vertex_float_count +
self.index_count +
self.src_image_count +
self.uniform_dword_count
}