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}