1use bytemuck::{Pod, Zeroable};
2use wgpu::util::DeviceExt;
3
4use super::camera::Camera2D;
5use super::gpu::GpuContext;
6use super::lighting::LightingUniform;
7use super::texture::TextureStore;
8
9#[derive(Debug, Clone)]
11pub struct SpriteCommand {
12 pub texture_id: u32,
13 pub x: f32,
14 pub y: f32,
15 pub w: f32,
16 pub h: f32,
17 pub layer: i32,
18 pub uv_x: f32,
19 pub uv_y: f32,
20 pub uv_w: f32,
21 pub uv_h: f32,
22 pub tint_r: f32,
23 pub tint_g: f32,
24 pub tint_b: f32,
25 pub tint_a: f32,
26}
27
28#[repr(C)]
30#[derive(Copy, Clone, Pod, Zeroable)]
31struct QuadVertex {
32 position: [f32; 2],
33 uv: [f32; 2],
34}
35
36#[repr(C)]
38#[derive(Copy, Clone, Pod, Zeroable)]
39struct SpriteInstance {
40 world_pos: [f32; 2],
41 size: [f32; 2],
42 uv_offset: [f32; 2],
43 uv_size: [f32; 2],
44 tint: [f32; 4],
45}
46
47#[repr(C)]
49#[derive(Copy, Clone, Pod, Zeroable)]
50struct CameraUniform {
51 view_proj: [f32; 16],
52}
53
54const QUAD_VERTICES: &[QuadVertex] = &[
56 QuadVertex { position: [0.0, 0.0], uv: [0.0, 0.0] }, QuadVertex { position: [1.0, 0.0], uv: [1.0, 0.0] }, QuadVertex { position: [1.0, 1.0], uv: [1.0, 1.0] }, QuadVertex { position: [0.0, 1.0], uv: [0.0, 1.0] }, ];
61
62const QUAD_INDICES: &[u16] = &[0, 1, 2, 0, 2, 3];
63
64pub struct SpritePipeline {
65 pipeline: wgpu::RenderPipeline,
66 vertex_buffer: wgpu::Buffer,
67 index_buffer: wgpu::Buffer,
68 camera_buffer: wgpu::Buffer,
69 camera_bind_group: wgpu::BindGroup,
70 pub texture_bind_group_layout: wgpu::BindGroupLayout,
71 lighting_buffer: wgpu::Buffer,
72 lighting_bind_group: wgpu::BindGroup,
73}
74
75impl SpritePipeline {
76 pub fn new(gpu: &GpuContext) -> Self {
77 let shader = gpu.device.create_shader_module(wgpu::ShaderModuleDescriptor {
78 label: Some("sprite_shader"),
79 source: wgpu::ShaderSource::Wgsl(
80 include_str!("shaders/sprite.wgsl").into(),
81 ),
82 });
83
84 let camera_bind_group_layout =
86 gpu.device
87 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
88 label: Some("camera_bind_group_layout"),
89 entries: &[wgpu::BindGroupLayoutEntry {
90 binding: 0,
91 visibility: wgpu::ShaderStages::VERTEX,
92 ty: wgpu::BindingType::Buffer {
93 ty: wgpu::BufferBindingType::Uniform,
94 has_dynamic_offset: false,
95 min_binding_size: None,
96 },
97 count: None,
98 }],
99 });
100
101 let texture_bind_group_layout =
103 gpu.device
104 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
105 label: Some("texture_bind_group_layout"),
106 entries: &[
107 wgpu::BindGroupLayoutEntry {
108 binding: 0,
109 visibility: wgpu::ShaderStages::FRAGMENT,
110 ty: wgpu::BindingType::Texture {
111 multisampled: false,
112 view_dimension: wgpu::TextureViewDimension::D2,
113 sample_type: wgpu::TextureSampleType::Float { filterable: true },
114 },
115 count: None,
116 },
117 wgpu::BindGroupLayoutEntry {
118 binding: 1,
119 visibility: wgpu::ShaderStages::FRAGMENT,
120 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
121 count: None,
122 },
123 ],
124 });
125
126 let lighting_bind_group_layout =
128 gpu.device
129 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
130 label: Some("lighting_bind_group_layout"),
131 entries: &[wgpu::BindGroupLayoutEntry {
132 binding: 0,
133 visibility: wgpu::ShaderStages::FRAGMENT,
134 ty: wgpu::BindingType::Buffer {
135 ty: wgpu::BufferBindingType::Uniform,
136 has_dynamic_offset: false,
137 min_binding_size: None,
138 },
139 count: None,
140 }],
141 });
142
143 let pipeline_layout =
144 gpu.device
145 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
146 label: Some("sprite_pipeline_layout"),
147 bind_group_layouts: &[
148 &camera_bind_group_layout,
149 &texture_bind_group_layout,
150 &lighting_bind_group_layout,
151 ],
152 push_constant_ranges: &[],
153 });
154
155 let vertex_layout = wgpu::VertexBufferLayout {
157 array_stride: std::mem::size_of::<QuadVertex>() as wgpu::BufferAddress,
158 step_mode: wgpu::VertexStepMode::Vertex,
159 attributes: &[
160 wgpu::VertexAttribute {
161 offset: 0,
162 shader_location: 0,
163 format: wgpu::VertexFormat::Float32x2,
164 },
165 wgpu::VertexAttribute {
166 offset: 8,
167 shader_location: 1,
168 format: wgpu::VertexFormat::Float32x2,
169 },
170 ],
171 };
172
173 let instance_layout = wgpu::VertexBufferLayout {
174 array_stride: std::mem::size_of::<SpriteInstance>() as wgpu::BufferAddress,
175 step_mode: wgpu::VertexStepMode::Instance,
176 attributes: &[
177 wgpu::VertexAttribute {
178 offset: 0,
179 shader_location: 2,
180 format: wgpu::VertexFormat::Float32x2, },
182 wgpu::VertexAttribute {
183 offset: 8,
184 shader_location: 3,
185 format: wgpu::VertexFormat::Float32x2, },
187 wgpu::VertexAttribute {
188 offset: 16,
189 shader_location: 4,
190 format: wgpu::VertexFormat::Float32x2, },
192 wgpu::VertexAttribute {
193 offset: 24,
194 shader_location: 5,
195 format: wgpu::VertexFormat::Float32x2, },
197 wgpu::VertexAttribute {
198 offset: 32,
199 shader_location: 6,
200 format: wgpu::VertexFormat::Float32x4, },
202 ],
203 };
204
205 let pipeline = gpu.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
206 label: Some("sprite_pipeline"),
207 layout: Some(&pipeline_layout),
208 vertex: wgpu::VertexState {
209 module: &shader,
210 entry_point: Some("vs_main"),
211 buffers: &[vertex_layout, instance_layout],
212 compilation_options: Default::default(),
213 },
214 fragment: Some(wgpu::FragmentState {
215 module: &shader,
216 entry_point: Some("fs_main"),
217 targets: &[Some(wgpu::ColorTargetState {
218 format: gpu.config.format,
219 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
220 write_mask: wgpu::ColorWrites::ALL,
221 })],
222 compilation_options: Default::default(),
223 }),
224 primitive: wgpu::PrimitiveState {
225 topology: wgpu::PrimitiveTopology::TriangleList,
226 strip_index_format: None,
227 front_face: wgpu::FrontFace::Ccw,
228 cull_mode: None,
229 polygon_mode: wgpu::PolygonMode::Fill,
230 unclipped_depth: false,
231 conservative: false,
232 },
233 depth_stencil: None,
234 multisample: wgpu::MultisampleState::default(),
235 multiview: None,
236 cache: None,
237 });
238
239 let vertex_buffer = gpu.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
240 label: Some("quad_vertex_buffer"),
241 contents: bytemuck::cast_slice(QUAD_VERTICES),
242 usage: wgpu::BufferUsages::VERTEX,
243 });
244
245 let index_buffer = gpu.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
246 label: Some("quad_index_buffer"),
247 contents: bytemuck::cast_slice(QUAD_INDICES),
248 usage: wgpu::BufferUsages::INDEX,
249 });
250
251 let camera_uniform = CameraUniform {
252 view_proj: Camera2D::default().view_proj(),
253 };
254
255 let camera_buffer = gpu.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
256 label: Some("camera_uniform_buffer"),
257 contents: bytemuck::cast_slice(&[camera_uniform]),
258 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
259 });
260
261 let camera_bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
262 label: Some("camera_bind_group"),
263 layout: &camera_bind_group_layout,
264 entries: &[wgpu::BindGroupEntry {
265 binding: 0,
266 resource: camera_buffer.as_entire_binding(),
267 }],
268 });
269
270 let default_lighting = LightingUniform {
272 ambient: [1.0, 1.0, 1.0],
273 light_count: 0,
274 lights: [super::lighting::LightData {
275 pos_radius: [0.0; 4],
276 color_intensity: [0.0; 4],
277 }; super::lighting::MAX_LIGHTS],
278 };
279
280 let lighting_buffer = gpu.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
281 label: Some("lighting_uniform_buffer"),
282 contents: bytemuck::cast_slice(&[default_lighting]),
283 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
284 });
285
286 let lighting_bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
287 label: Some("lighting_bind_group"),
288 layout: &lighting_bind_group_layout,
289 entries: &[wgpu::BindGroupEntry {
290 binding: 0,
291 resource: lighting_buffer.as_entire_binding(),
292 }],
293 });
294
295 Self {
296 pipeline,
297 vertex_buffer,
298 index_buffer,
299 camera_buffer,
300 camera_bind_group,
301 texture_bind_group_layout,
302 lighting_buffer,
303 lighting_bind_group,
304 }
305 }
306
307 pub fn render(
309 &self,
310 gpu: &GpuContext,
311 textures: &TextureStore,
312 camera: &Camera2D,
313 lighting: &LightingUniform,
314 commands: &[SpriteCommand],
315 target: &wgpu::TextureView,
316 encoder: &mut wgpu::CommandEncoder,
317 ) {
318 let camera_uniform = CameraUniform {
320 view_proj: camera.view_proj(),
321 };
322 gpu.queue.write_buffer(
323 &self.camera_buffer,
324 0,
325 bytemuck::cast_slice(&[camera_uniform]),
326 );
327
328 gpu.queue.write_buffer(
330 &self.lighting_buffer,
331 0,
332 bytemuck::cast_slice(&[*lighting]),
333 );
334
335 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
336 label: Some("sprite_render_pass"),
337 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
338 view: target,
339 resolve_target: None,
340 ops: wgpu::Operations {
341 load: wgpu::LoadOp::Clear(wgpu::Color {
342 r: 0.1,
343 g: 0.1,
344 b: 0.15,
345 a: 1.0,
346 }),
347 store: wgpu::StoreOp::Store,
348 },
349 })],
350 depth_stencil_attachment: None,
351 timestamp_writes: None,
352 occlusion_query_set: None,
353 });
354
355 render_pass.set_pipeline(&self.pipeline);
356 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
357 render_pass.set_bind_group(2, &self.lighting_bind_group, &[]);
358 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
359 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
360
361 let mut i = 0;
363 while i < commands.len() {
364 let tex_id = commands[i].texture_id;
365 let batch_start = i;
366 while i < commands.len() && commands[i].texture_id == tex_id {
367 i += 1;
368 }
369 let batch = &commands[batch_start..i];
370
371 let bind_group = match textures.get_bind_group(tex_id) {
373 Some(bg) => bg,
374 None => continue, };
376
377 let instances: Vec<SpriteInstance> = batch
379 .iter()
380 .map(|cmd| SpriteInstance {
381 world_pos: [cmd.x, cmd.y],
382 size: [cmd.w, cmd.h],
383 uv_offset: [cmd.uv_x, cmd.uv_y],
384 uv_size: [cmd.uv_w, cmd.uv_h],
385 tint: [cmd.tint_r, cmd.tint_g, cmd.tint_b, cmd.tint_a],
386 })
387 .collect();
388
389 let instance_buffer =
390 gpu.device
391 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
392 label: Some("sprite_instance_buffer"),
393 contents: bytemuck::cast_slice(&instances),
394 usage: wgpu::BufferUsages::VERTEX,
395 });
396
397 render_pass.set_bind_group(1, bind_group, &[]);
398 render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
399 render_pass.draw_indexed(0..6, 0, 0..instances.len() as u32);
400 }
401 }
402}