1use bytemuck::{Pod, Zeroable};
6use image::RgbaImage;
7use limnus_wgpu_math::{Matrix4, Vec4};
8use wgpu::BufferBindingType;
9use wgpu::util::DeviceExt;
10use wgpu::{
11 BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
12 BindGroupLayoutEntry, BindingType, Buffer, BufferAddress, BufferDescriptor, BufferUsages,
13 Device, Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, PipelineLayout,
14 PipelineLayoutDescriptor, Queue, RenderPipeline, RenderPipelineDescriptor, Sampler,
15 SamplerBindingType, ShaderModule, ShaderStages, Texture, TextureAspect, TextureDescriptor,
16 TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor,
17 TextureViewDimension, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode,
18};
19use wgpu::{BindingResource, PipelineCompilationOptions};
20use wgpu::{
21 BlendState, ColorTargetState, ColorWrites, Face, FrontFace, MultisampleState, PolygonMode,
22 PrimitiveState, PrimitiveTopology, util,
23};
24
25#[repr(C)]
26#[derive(Copy, Clone, Debug)]
27struct Vertex {
28 position: [f32; 2], tex_coords: [f32; 2], }
31
32unsafe impl Zeroable for Vertex {}
34
35unsafe impl Pod for Vertex {}
37
38impl Vertex {
39 const ATTRIBUTES: [VertexAttribute; 2] =
40 wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
41
42 pub const fn desc() -> VertexBufferLayout<'static> {
43 VertexBufferLayout {
44 array_stride: size_of::<Self>() as BufferAddress,
45 step_mode: VertexStepMode::Vertex,
46 attributes: &Self::ATTRIBUTES,
47 }
48 }
49}
50
51#[must_use]
55pub fn create_sprite_uniform_buffer(device: &Device, label: &str) -> Buffer {
56 device.create_buffer_init(&util::BufferInitDescriptor {
57 label: Some(label),
58 contents: bytemuck::cast_slice(&[SpriteInstanceUniform {
59 model: Matrix4::identity(),
60 tex_coords_mul_add: Vec4([0.0, 0.0, 1.0, 1.0]),
61 rotation: 0,
62 color: Vec4([1.0, 0.0, 1.0, 1.0]),
63 use_texture: 0,
64 }]),
65 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
66 })
67}
68
69#[repr(C)]
70#[derive(Copy, Clone)]
71pub struct CameraUniform {
72 pub view_proj: Matrix4,
73}
74
75unsafe impl Pod for CameraUniform {}
76unsafe impl Zeroable for CameraUniform {}
77
78#[repr(C)]
79#[derive(Copy, Clone)]
80pub struct SpriteInstanceUniform {
81 pub model: Matrix4, pub tex_coords_mul_add: Vec4,
83 pub rotation: u32,
84 pub color: Vec4,
85 pub use_texture: u32,
86}
87
88unsafe impl Pod for SpriteInstanceUniform {}
89unsafe impl Zeroable for SpriteInstanceUniform {}
90
91impl SpriteInstanceUniform {
92 #[must_use]
93 pub const fn new(
94 model: Matrix4,
95 tex_coords_mul_add: Vec4,
96 rotation: u32,
97 color: Vec4,
98 use_texture: bool,
99 ) -> Self {
100 Self {
101 model,
102 tex_coords_mul_add,
103 rotation,
104 color,
105 use_texture: use_texture as u32,
106 }
107 }
108}
109
110impl SpriteInstanceUniform {
111 const fn desc<'a>() -> VertexBufferLayout<'a> {
112 VertexBufferLayout {
113 array_stride: size_of::<Self>() as BufferAddress,
114 step_mode: VertexStepMode::Instance,
115 attributes: &[
116 VertexAttribute {
118 offset: 0,
119 shader_location: 2,
120 format: VertexFormat::Float32x4,
121 },
122 VertexAttribute {
123 offset: 16,
124 shader_location: 3,
125 format: VertexFormat::Float32x4,
126 },
127 VertexAttribute {
128 offset: 32,
129 shader_location: 4,
130 format: VertexFormat::Float32x4,
131 },
132 VertexAttribute {
133 offset: 48,
134 shader_location: 5,
135 format: VertexFormat::Float32x4,
136 },
137 VertexAttribute {
139 offset: 64,
140 shader_location: 6,
141 format: VertexFormat::Float32x4,
142 },
143 VertexAttribute {
145 offset: 80,
146 shader_location: 7,
147 format: VertexFormat::Uint32,
148 },
149 VertexAttribute {
151 offset: 84,
152 shader_location: 8,
153 format: VertexFormat::Float32x4,
154 },
155 VertexAttribute {
157 offset: 84 + 4 * 4,
158 shader_location: 9,
159 format: VertexFormat::Uint32,
160 },
161 ],
162 }
163 }
164}
165
166const RIGHT: f32 = 1.0;
168const DOWN: f32 = 1.0;
169
170const IDENTITY_QUAD_VERTICES: &[Vertex] = &[
171 Vertex {
172 position: [0.0, 0.0],
173 tex_coords: [0.0, DOWN],
174 }, Vertex {
176 position: [1.0, 0.0],
177 tex_coords: [RIGHT, DOWN],
178 }, Vertex {
180 position: [1.0, 1.0],
181 tex_coords: [RIGHT, 0.0],
182 }, Vertex {
184 position: [0.0, 1.0],
185 tex_coords: [0.0, 0.0],
186 }, ];
188
189pub const INDICES: &[u16] = &[0, 1, 2, 0, 2, 3];
191
192#[derive(Debug)]
193pub struct SpriteInfo {
194 pub sprite_pipeline: RenderPipeline,
195
196 pub sampler: Sampler,
197 pub vertex_buffer: Buffer,
198 pub index_buffer: Buffer,
199
200 pub camera_bind_group_layout: BindGroupLayout,
202
203 pub camera_uniform_buffer: Buffer,
204 pub camera_bind_group: BindGroup,
205
206 pub sprite_texture_sampler_bind_group_layout: BindGroupLayout,
208
209 pub quad_matrix_and_uv_instance_buffer: Buffer,
211}
212
213const MAX_RENDER_SPRITE_COUNT: usize = 10_000;
214
215impl SpriteInfo {
216 #[must_use]
217 pub fn new(
218 device: &Device,
219 surface_texture_format: TextureFormat,
220 vertex_shader_source: &str,
221 fragment_shader_source: &str,
222 view_proj_matrix: Matrix4,
223 ) -> Self {
224 let vertex_shader =
225 swamp_wgpu::create_shader_module(device, "sprite vertex", vertex_shader_source);
226 let fragment_shader =
227 swamp_wgpu::create_shader_module(device, "sprite fragment", fragment_shader_source);
228
229 let index_buffer = create_sprite_index_buffer(device, "identity quad index buffer");
230 let vertex_buffer = create_sprite_vertex_buffer(device, "identity quad vertex buffer");
231
232 let camera_uniform_buffer = create_camera_uniform_buffer(
234 device,
235 view_proj_matrix,
236 "view and projection matrix (camera)",
237 );
238
239 let camera_bind_group_layout =
240 create_camera_uniform_bind_group_layout(device, "camera bind group layout");
241
242 let camera_bind_group = create_camera_uniform_bind_group(
243 device,
244 &camera_bind_group_layout,
245 &camera_uniform_buffer,
246 "camera matrix",
247 );
248
249 let sprite_texture_sampler_bind_group_layout = create_sprite_texture_sampler_group_layout(
251 device,
252 "texture and sampler bind group layout",
253 );
254
255 let quad_matrix_and_uv_instance_buffer = create_quad_matrix_and_uv_instance_buffer(
257 device,
258 MAX_RENDER_SPRITE_COUNT,
259 "sprite_instance buffer",
260 );
261
262 let sprite_pipeline_layout = create_sprite_pipeline_layout(
263 device,
264 &camera_bind_group_layout,
265 &sprite_texture_sampler_bind_group_layout,
266 "sprite pipeline layout",
267 );
268
269 let sprite_pipeline = create_sprite_pipeline(
270 device,
271 surface_texture_format,
272 &sprite_pipeline_layout,
273 &vertex_shader,
274 &fragment_shader,
275 );
276
277 let sampler = swamp_wgpu::create_nearest_sampler(device, "sprite nearest sampler");
278
279 Self {
280 sprite_pipeline,
281 sampler,
282 vertex_buffer,
283 index_buffer,
284 camera_bind_group_layout,
285 camera_uniform_buffer,
286 camera_bind_group,
287 sprite_texture_sampler_bind_group_layout,
288 quad_matrix_and_uv_instance_buffer,
289 }
290 }
291}
292
293fn create_camera_uniform_buffer(device: &Device, view_proj: Matrix4, label: &str) -> Buffer {
295 let camera_uniform = CameraUniform { view_proj };
296
297 device.create_buffer_init(&util::BufferInitDescriptor {
298 label: Some(label),
299 contents: bytemuck::cast_slice(&[camera_uniform]),
300 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
301 })
302}
303
304fn create_camera_uniform_bind_group_layout(device: &Device, label: &str) -> BindGroupLayout {
306 device.create_bind_group_layout(&BindGroupLayoutDescriptor {
307 label: Some(label),
308 entries: &[BindGroupLayoutEntry {
309 binding: 0,
310 visibility: ShaderStages::VERTEX,
311 ty: BindingType::Buffer {
312 ty: BufferBindingType::Uniform,
313 has_dynamic_offset: false,
314 min_binding_size: None,
315 },
316 count: None,
317 }],
318 })
319}
320
321fn create_camera_uniform_bind_group(
322 device: &Device,
323 bind_group_layout: &BindGroupLayout,
324 uniform_buffer: &Buffer,
325 label: &str,
326) -> BindGroup {
327 device.create_bind_group(&BindGroupDescriptor {
328 label: Some(label),
329 layout: bind_group_layout,
330 entries: &[BindGroupEntry {
331 binding: 0,
332 resource: uniform_buffer.as_entire_binding(),
333 }],
334 })
335}
336
337#[must_use]
338pub fn load_texture_from_memory(
339 device: &Device,
340 queue: &Queue,
341 img: &RgbaImage,
342 label: &str,
343) -> Texture {
344 let (width, height) = img.dimensions();
345
346 let texture = device.create_texture(&TextureDescriptor {
348 label: Some(label),
349 size: Extent3d {
350 width,
351 height,
352 depth_or_array_layers: 1,
353 },
354 mip_level_count: 1,
355 sample_count: 1,
356 dimension: TextureDimension::D2,
357 format: TextureFormat::Rgba8UnormSrgb,
358 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
359 view_formats: &[TextureFormat::Rgba8UnormSrgb],
360 });
361
362 queue.write_texture(
363 ImageCopyTexture {
364 texture: &texture,
365 mip_level: 0,
366 origin: Origin3d::ZERO,
367 aspect: TextureAspect::All,
368 },
369 img,
370 ImageDataLayout {
371 offset: 0,
372 bytes_per_row: Some(4 * width),
373 rows_per_image: Some(height),
374 },
375 Extent3d {
376 width,
377 height,
378 depth_or_array_layers: 1,
379 },
380 );
381
382 texture
383}
384
385#[must_use]
386pub fn create_sprite_vertex_buffer(device: &Device, label: &str) -> Buffer {
387 device.create_buffer_init(&util::BufferInitDescriptor {
388 label: Some(label),
389 contents: bytemuck::cast_slice(IDENTITY_QUAD_VERTICES),
390 usage: BufferUsages::VERTEX,
391 })
392}
393
394#[must_use]
395pub fn create_sprite_index_buffer(device: &Device, label: &str) -> Buffer {
396 device.create_buffer_init(&util::BufferInitDescriptor {
397 label: Some(label),
398 contents: bytemuck::cast_slice(INDICES),
399 usage: BufferUsages::INDEX,
400 })
401}
402
403#[must_use]
406pub fn create_sprite_texture_sampler_group_layout(device: &Device, label: &str) -> BindGroupLayout {
407 device.create_bind_group_layout(&BindGroupLayoutDescriptor {
408 label: Some(label),
409 entries: &[
410 BindGroupLayoutEntry {
411 binding: 0,
412 visibility: ShaderStages::FRAGMENT,
413 ty: BindingType::Texture {
414 multisampled: false,
415 view_dimension: TextureViewDimension::D2,
416 sample_type: TextureSampleType::Float { filterable: true },
417 },
418 count: None,
419 },
420 BindGroupLayoutEntry {
421 binding: 1,
422 visibility: ShaderStages::FRAGMENT,
423 ty: BindingType::Sampler(SamplerBindingType::Filtering),
424 count: None,
425 },
426 ],
427 })
428}
429
430#[must_use]
431pub fn create_sprite_texture_and_sampler_bind_group(
432 device: &Device,
433 bind_group_layout: &BindGroupLayout,
434 texture: &Texture,
435 sampler: &Sampler,
436 label: &str,
437) -> BindGroup {
438 let texture_view = texture.create_view(&TextureViewDescriptor::default());
439 device.create_bind_group(&BindGroupDescriptor {
440 layout: bind_group_layout,
441 entries: &[
442 BindGroupEntry {
443 binding: 0,
444 resource: BindingResource::TextureView(&texture_view),
445 },
446 BindGroupEntry {
447 binding: 1,
448 resource: BindingResource::Sampler(sampler),
449 },
450 ],
451 label: Some(label),
452 })
453}
454
455#[must_use]
456pub fn create_quad_matrix_and_uv_instance_buffer(
457 device: &Device,
458 max_instances: usize,
459 label: &str,
460) -> Buffer {
461 let buffer_size = (size_of::<SpriteInstanceUniform>() * max_instances) as BufferAddress;
462
463 device.create_buffer(&BufferDescriptor {
464 label: Some(label),
465 size: buffer_size,
466 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
467 mapped_at_creation: false,
468 })
469}
470
471fn create_sprite_pipeline_layout(
472 device: &Device,
473 camera_bind_group_layout: &BindGroupLayout,
474 texture_sampler_group_layout: &BindGroupLayout,
475 label: &str,
476) -> PipelineLayout {
477 device.create_pipeline_layout(&PipelineLayoutDescriptor {
478 label: Some(label),
479 bind_group_layouts: &[camera_bind_group_layout, texture_sampler_group_layout],
480 push_constant_ranges: &[],
481 })
482}
483
484fn create_sprite_pipeline(
485 device: &Device,
486 format: TextureFormat,
487 pipeline_layout: &PipelineLayout,
488 vertex_shader: &ShaderModule,
489 fragment_shader: &ShaderModule,
490) -> RenderPipeline {
491 device.create_render_pipeline(&RenderPipelineDescriptor {
492 label: Some("sprite alpha blend pipeline"),
493 layout: Some(pipeline_layout),
494 vertex: wgpu::VertexState {
495 module: vertex_shader,
496 entry_point: Some("vs_main"),
497 compilation_options: PipelineCompilationOptions::default(),
498 buffers: &[Vertex::desc(), SpriteInstanceUniform::desc()],
499 },
500 fragment: Some(wgpu::FragmentState {
501 module: fragment_shader,
502 entry_point: Some("fs_main"),
503 compilation_options: PipelineCompilationOptions::default(),
504 targets: &[Some(ColorTargetState {
505 format,
506 blend: Some(BlendState::ALPHA_BLENDING),
507 write_mask: ColorWrites::ALL,
508 })],
509 }),
510 primitive: PrimitiveState {
511 topology: PrimitiveTopology::TriangleList,
512 strip_index_format: None,
513 front_face: FrontFace::Ccw,
514 cull_mode: Some(Face::Back),
515 unclipped_depth: false,
516 polygon_mode: PolygonMode::Fill,
517 conservative: false,
518 },
519
520 depth_stencil: None,
521 multisample: MultisampleState::default(),
522 multiview: None,
523 cache: None,
524 })
525}