Skip to main content

astrelis_render/batched/
types.rs

1//! Core types for the batched deferred renderer.
2//!
3//! All three render tiers share a unified instance format and draw batch structure.
4
5use std::sync::Arc;
6
7use bytemuck::{Pod, Zeroable};
8
9/// Render tier describing GPU feature availability.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum RenderTier {
12    /// Tier 1: Per-clip, per-texture `draw()` calls. No special features required.
13    Direct,
14    /// Tier 2: `multi_draw_indirect()` per texture group.
15    /// Requires `INDIRECT_FIRST_INSTANCE`.
16    Indirect,
17    /// Tier 3: Single `multi_draw_indirect()` per frame using bindless textures.
18    /// Requires `INDIRECT_FIRST_INSTANCE` + `TEXTURE_BINDING_ARRAY` + `PARTIALLY_BOUND_BINDING_ARRAY`.
19    Bindless,
20}
21
22impl std::fmt::Display for RenderTier {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            RenderTier::Direct => write!(f, "Direct (Tier 1)"),
26            RenderTier::Indirect => write!(f, "Indirect (Tier 2)"),
27            RenderTier::Bindless => write!(f, "Bindless (Tier 3)"),
28        }
29    }
30}
31
32/// Draw type discriminant for the unified instance format.
33#[repr(u32)]
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum DrawType2D {
36    /// Solid quad with SDF rounded rectangle. No texture sampling.
37    Quad = 0,
38    /// Text glyph: R8 atlas alpha multiplied by instance color.
39    Text = 1,
40    /// Image: RGBA texture multiplied by tint color.
41    Image = 2,
42}
43
44/// Unified instance data shared by all three render tiers.
45///
46/// 96 bytes total, 16-byte aligned. Encodes quads, text glyphs, and images
47/// as textured/untextured quads differentiated by `draw_type`.
48#[repr(C)]
49#[derive(Debug, Clone, Copy)]
50pub struct UnifiedInstance2D {
51    /// Screen-space position (top-left corner).
52    pub position: [f32; 2],
53    /// Quad size in pixels.
54    pub size: [f32; 2],
55    /// Texture UV min (0,0 for solid quads).
56    pub uv_min: [f32; 2],
57    /// Texture UV max (0,0 for solid quads).
58    pub uv_max: [f32; 2],
59    /// Fill/tint/text color (RGBA, premultiplied alpha).
60    pub color: [f32; 4],
61    /// SDF corner radius in pixels.
62    pub border_radius: f32,
63    /// Border outline thickness (0 = filled).
64    pub border_thickness: f32,
65    /// Index into the texture array (0 for text atlas, 1..N for images).
66    pub texture_index: u32,
67    /// Draw type discriminant: 0=quad, 1=text, 2=image.
68    pub draw_type: u32,
69    /// Shader-based clip rect min (screen space).
70    pub clip_min: [f32; 2],
71    /// Shader-based clip rect max (screen space).
72    pub clip_max: [f32; 2],
73    /// Normalized depth (0.0=far, 1.0=near). Higher z_index maps to higher z_depth.
74    pub z_depth: f32,
75    /// Reserved for future use (rotation, flags, custom_data).
76    pub _reserved: [f32; 3],
77}
78
79// SAFETY: UnifiedInstance is repr(C) with only f32 and u32 fields, no padding holes
80unsafe impl Pod for UnifiedInstance2D {}
81unsafe impl Zeroable for UnifiedInstance2D {}
82
83impl Default for UnifiedInstance2D {
84    fn default() -> Self {
85        Self {
86            position: [0.0; 2],
87            size: [0.0; 2],
88            uv_min: [0.0; 2],
89            uv_max: [0.0; 2],
90            color: [1.0, 1.0, 1.0, 1.0],
91            border_radius: 0.0,
92            border_thickness: 0.0,
93            texture_index: 0,
94            draw_type: DrawType2D::Quad as u32,
95            clip_min: [f32::NEG_INFINITY, f32::NEG_INFINITY],
96            clip_max: [f32::INFINITY, f32::INFINITY],
97            z_depth: 0.0,
98            _reserved: [0.0; 3],
99        }
100    }
101}
102
103impl UnifiedInstance2D {
104    /// Returns the wgpu vertex buffer layout for instanced rendering.
105    pub fn layout() -> wgpu::VertexBufferLayout<'static> {
106        const ATTRS: &[wgpu::VertexAttribute] = &wgpu::vertex_attr_array![
107            // location 2: position (vec2)
108            2 => Float32x2,
109            // location 3: size (vec2)
110            3 => Float32x2,
111            // location 4: uv_min (vec2)
112            4 => Float32x2,
113            // location 5: uv_max (vec2)
114            5 => Float32x2,
115            // location 6: color (vec4)
116            6 => Float32x4,
117            // location 7: border_radius (f32)
118            7 => Float32,
119            // location 8: border_thickness (f32)
120            8 => Float32,
121            // location 9: texture_index (u32)
122            9 => Uint32,
123            // location 10: draw_type (u32)
124            10 => Uint32,
125            // location 11: clip_min (vec2)
126            11 => Float32x2,
127            // location 12: clip_max (vec2)
128            12 => Float32x2,
129            // location 13: z_depth (f32)
130            13 => Float32,
131            // location 14: _reserved (vec3)
132            14 => Float32x3,
133        ];
134
135        wgpu::VertexBufferLayout {
136            array_stride: std::mem::size_of::<UnifiedInstance2D>() as wgpu::BufferAddress,
137            step_mode: wgpu::VertexStepMode::Instance,
138            attributes: ATTRS,
139        }
140    }
141
142    /// Size of the instance in bytes.
143    pub const SIZE: u64 = std::mem::size_of::<Self>() as u64;
144}
145
146/// A texture slot in the draw batch.
147pub struct TextureSlot2D {
148    /// Stable ID for cache keying.
149    pub id: u64,
150    /// The texture view to bind.
151    pub view: Arc<wgpu::TextureView>,
152    /// The sampler to use.
153    pub sampler: Arc<wgpu::Sampler>,
154}
155
156/// A complete draw batch submitted to the renderer each frame.
157pub struct DrawBatch2D {
158    /// Instances sorted by (draw_type, texture_index) for efficient batching.
159    pub instances: Vec<UnifiedInstance2D>,
160    /// Texture slots. Index 0 is typically the text atlas (R8), 1..N are images (RGBA).
161    pub textures: Vec<TextureSlot2D>,
162    /// Orthographic projection matrix.
163    pub projection: [[f32; 4]; 4],
164}
165
166/// Rendering statistics from the last frame.
167#[derive(Debug, Clone, Copy, Default)]
168pub struct BatchRenderStats2D {
169    /// Total number of instances rendered.
170    pub instance_count: u32,
171    /// Number of opaque instances.
172    pub opaque_count: u32,
173    /// Number of transparent instances.
174    pub transparent_count: u32,
175    /// Number of GPU draw calls issued.
176    pub draw_calls: u32,
177    /// Number of bind group switches.
178    pub bind_group_switches: u32,
179    /// Number of pipeline switches.
180    pub pipeline_switches: u32,
181    /// Number of textures bound.
182    pub texture_count: u32,
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_unified_instance_size() {
191        assert_eq!(std::mem::size_of::<UnifiedInstance2D>(), 96);
192    }
193
194    #[test]
195    fn test_unified_instance_alignment() {
196        assert!(std::mem::align_of::<UnifiedInstance2D>() <= 16);
197    }
198
199    #[test]
200    fn test_draw_type_values() {
201        assert_eq!(DrawType2D::Quad as u32, 0);
202        assert_eq!(DrawType2D::Text as u32, 1);
203        assert_eq!(DrawType2D::Image as u32, 2);
204    }
205}