shadow_engine_2d/
graphics.rs

1use crate::ecs::World;
2use crate::math::{Color, Transform, Vec2};
3use crate::ui::{UIPanel, UIProgressBar, UITransform};
4use std::sync::Arc;
5use wgpu::util::DeviceExt;
6use winit::window::Window;
7use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
8
9pub struct Renderer {
10    surface: wgpu::Surface<'static>,
11    device: wgpu::Device,
12    queue: wgpu::Queue,
13    config: wgpu::SurfaceConfiguration,
14    size: winit::dpi::PhysicalSize<u32>,
15    render_pipeline: wgpu::RenderPipeline,
16    #[allow(dead_code)]
17    font_system: FontSystem,
18    #[allow(dead_code)]
19    swash_cache: SwashCache,
20    #[allow(dead_code)]
21    text_atlas: TextAtlas,
22    #[allow(dead_code)]
23    text_renderer: TextRenderer,
24}
25
26#[repr(C)]
27#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
28struct Vertex {
29    position: [f32; 2],
30    color: [f32; 4],
31}
32
33impl Vertex {
34    fn desc() -> wgpu::VertexBufferLayout<'static> {
35        wgpu::VertexBufferLayout {
36            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
37            step_mode: wgpu::VertexStepMode::Vertex,
38            attributes: &[
39                wgpu::VertexAttribute {
40                    offset: 0,
41                    shader_location: 0,
42                    format: wgpu::VertexFormat::Float32x2,
43                },
44                wgpu::VertexAttribute {
45                    offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
46                    shader_location: 1,
47                    format: wgpu::VertexFormat::Float32x4,
48                },
49            ],
50        }
51    }
52}
53
54impl Renderer {
55    pub async fn new(window: Arc<Window>) -> Self {
56        let size = window.inner_size();
57
58        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
59            backends: wgpu::Backends::all(),
60            ..Default::default()
61        });
62
63        let surface = instance.create_surface(window).unwrap();
64
65        let adapter = instance
66            .request_adapter(&wgpu::RequestAdapterOptions {
67                power_preference: wgpu::PowerPreference::default(),
68                compatible_surface: Some(&surface),
69                force_fallback_adapter: false,
70            })
71            .await
72            .unwrap();
73
74        let (device, queue) = adapter
75            .request_device(
76                &wgpu::DeviceDescriptor {
77                    label: None,
78                    required_features: wgpu::Features::empty(),
79                    required_limits: wgpu::Limits::default(),
80                },
81                None,
82            )
83            .await
84            .unwrap();
85
86        let surface_caps = surface.get_capabilities(&adapter);
87        let surface_format = surface_caps
88            .formats
89            .iter()
90            .copied()
91            .find(|f| f.is_srgb())
92            .unwrap_or(surface_caps.formats[0]);
93
94        let config = wgpu::SurfaceConfiguration {
95            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
96            format: surface_format,
97            width: size.width,
98            height: size.height,
99            present_mode: surface_caps.present_modes[0],
100            alpha_mode: surface_caps.alpha_modes[0],
101            view_formats: vec![],
102            desired_maximum_frame_latency: 2,
103        };
104
105        surface.configure(&device, &config);
106
107        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
108            label: Some("Shader"),
109            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/basic.wgsl").into()),
110        });
111
112        let render_pipeline_layout =
113            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
114                label: Some("Render Pipeline Layout"),
115                bind_group_layouts: &[],
116                push_constant_ranges: &[],
117            });
118
119        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
120            label: Some("Render Pipeline"),
121            layout: Some(&render_pipeline_layout),
122            vertex: wgpu::VertexState {
123                module: &shader,
124                entry_point: "vs_main",
125                buffers: &[Vertex::desc()],
126            },
127            fragment: Some(wgpu::FragmentState {
128                module: &shader,
129                entry_point: "fs_main",
130                targets: &[Some(wgpu::ColorTargetState {
131                    format: config.format,
132                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
133                    write_mask: wgpu::ColorWrites::ALL,
134                })],
135            }),
136            primitive: wgpu::PrimitiveState {
137                topology: wgpu::PrimitiveTopology::TriangleList,
138                strip_index_format: None,
139                front_face: wgpu::FrontFace::Ccw,
140                cull_mode: Some(wgpu::Face::Back),
141                polygon_mode: wgpu::PolygonMode::Fill,
142                unclipped_depth: false,
143                conservative: false,
144            },
145            depth_stencil: None,
146            multisample: wgpu::MultisampleState {
147                count: 1,
148                mask: !0,
149                alpha_to_coverage_enabled: false,
150            },
151            multiview: None,
152        });
153
154        let font_system = FontSystem::new();
155        let swash_cache = SwashCache::new();
156        let mut cache = TextAtlas::new(&device, &queue, config.format);
157        let text_renderer = TextRenderer::new(&mut cache, &device, wgpu::MultisampleState::default(), None);
158
159        Self {
160            surface,
161            device,
162            queue,
163            config,
164            size,
165            render_pipeline,
166            font_system,
167            swash_cache,
168            text_atlas: cache,
169            text_renderer,
170        }
171    }
172
173    pub fn size(&self) -> winit::dpi::PhysicalSize<u32> {
174        self.size
175    }
176
177    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
178        if new_size.width > 0 && new_size.height > 0 {
179            self.size = new_size;
180            self.config.width = new_size.width;
181            self.config.height = new_size.height;
182            self.surface.configure(&self.device, &self.config);
183        }
184    }
185
186    pub fn render(&mut self, world: &World) -> Result<(), wgpu::SurfaceError> {
187        let output = self.surface.get_current_texture()?;
188        let view = output
189            .texture
190            .create_view(&wgpu::TextureViewDescriptor::default());
191
192        let mut encoder = self
193            .device
194            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
195                label: Some("Render Encoder"),
196            });
197
198        // Collect all buffers before render pass
199        let mut buffers = Vec::new();
200        
201        // Render sprites
202        for entity in world.entities() {
203            if let (Some(transform), Some(sprite)) =
204                (entity.get::<Transform>(), entity.get::<Sprite>())
205            {
206                let vertices = self.create_sprite_vertices(transform, sprite);
207                let vertex_buffer =
208                    self.device
209                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
210                            label: Some("Vertex Buffer"),
211                            contents: bytemuck::cast_slice(&vertices),
212                            usage: wgpu::BufferUsages::VERTEX,
213                        });
214
215                let indices: &[u16] = &[0, 1, 2, 0, 2, 3];
216                let index_buffer =
217                    self.device
218                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
219                            label: Some("Index Buffer"),
220                            contents: bytemuck::cast_slice(indices),
221                            usage: wgpu::BufferUsages::INDEX,
222                        });
223
224                buffers.push((vertex_buffer, index_buffer));
225            }
226        }
227
228        // Render UI panels
229        for entity in world.entities() {
230            if let (Some(ui_transform), Some(panel)) =
231                (entity.get::<UITransform>(), entity.get::<UIPanel>())
232            {
233                let pos = ui_transform.get_screen_position(self.size.width as f32, self.size.height as f32);
234                let transform = Transform::new(
235                    pos.x + ui_transform.size.x / 2.0 - self.size.width as f32 / 2.0,
236                    pos.y + ui_transform.size.y / 2.0 - self.size.height as f32 / 2.0
237                );
238                let sprite = Sprite {
239                    size: ui_transform.size,
240                    color: panel.background_color,
241                };
242                
243                let vertices = self.create_sprite_vertices(&transform, &sprite);
244                let vertex_buffer =
245                    self.device
246                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
247                            label: Some("UI Vertex Buffer"),
248                            contents: bytemuck::cast_slice(&vertices),
249                            usage: wgpu::BufferUsages::VERTEX,
250                        });
251
252                let indices: &[u16] = &[0, 1, 2, 0, 2, 3];
253                let index_buffer =
254                    self.device
255                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
256                            label: Some("UI Index Buffer"),
257                            contents: bytemuck::cast_slice(indices),
258                            usage: wgpu::BufferUsages::INDEX,
259                        });
260
261                buffers.push((vertex_buffer, index_buffer));
262            }
263        }
264
265        // Render UI progress bars
266        for entity in world.entities() {
267            if let (Some(ui_transform), Some(bar)) =
268                (entity.get::<UITransform>(), entity.get::<UIProgressBar>())
269            {
270                let pos = ui_transform.get_screen_position(self.size.width as f32, self.size.height as f32);
271                
272                // Background
273                let bg_transform = Transform::new(
274                    pos.x + ui_transform.size.x / 2.0 - self.size.width as f32 / 2.0,
275                    pos.y + ui_transform.size.y / 2.0 - self.size.height as f32 / 2.0
276                );
277                let bg_sprite = Sprite {
278                    size: ui_transform.size,
279                    color: bar.background_color,
280                };
281                
282                let vertices = self.create_sprite_vertices(&bg_transform, &bg_sprite);
283                let vertex_buffer =
284                    self.device
285                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
286                            label: Some("UI Bar BG Vertex Buffer"),
287                            contents: bytemuck::cast_slice(&vertices),
288                            usage: wgpu::BufferUsages::VERTEX,
289                        });
290
291                let indices: &[u16] = &[0, 1, 2, 0, 2, 3];
292                let index_buffer =
293                    self.device
294                        .create_buffer_init(&wgpu::util::BufferInitDescriptor {
295                            label: Some("UI Bar BG Index Buffer"),
296                            contents: bytemuck::cast_slice(indices),
297                            usage: wgpu::BufferUsages::INDEX,
298                        });
299
300                buffers.push((vertex_buffer, index_buffer));
301
302                // Fill
303                let fill_width = ui_transform.size.x * bar.get_fill_percentage();
304                if fill_width > 0.0 {
305                    let fill_transform = Transform::new(
306                        pos.x + fill_width / 2.0 - self.size.width as f32 / 2.0,
307                        pos.y + ui_transform.size.y / 2.0 - self.size.height as f32 / 2.0
308                    );
309                    let fill_sprite = Sprite {
310                        size: Vec2::new(fill_width, ui_transform.size.y),
311                        color: bar.fill_color,
312                    };
313                    
314                    let vertices = self.create_sprite_vertices(&fill_transform, &fill_sprite);
315                    let vertex_buffer =
316                        self.device
317                            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
318                                label: Some("UI Bar Fill Vertex Buffer"),
319                                contents: bytemuck::cast_slice(&vertices),
320                                usage: wgpu::BufferUsages::VERTEX,
321                            });
322
323                    let indices: &[u16] = &[0, 1, 2, 0, 2, 3];
324                    let index_buffer =
325                        self.device
326                            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
327                                label: Some("UI Bar Fill Index Buffer"),
328                                contents: bytemuck::cast_slice(indices),
329                                usage: wgpu::BufferUsages::INDEX,
330                            });
331
332                    buffers.push((vertex_buffer, index_buffer));
333                }
334            }
335        }
336
337        {
338            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
339                label: Some("Render Pass"),
340                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
341                    view: &view,
342                    resolve_target: None,
343                    ops: wgpu::Operations {
344                        load: wgpu::LoadOp::Clear(wgpu::Color {
345                            r: 0.1,
346                            g: 0.1,
347                            b: 0.1,
348                            a: 1.0,
349                        }),
350                        store: wgpu::StoreOp::Store,
351                    },
352                })],
353                depth_stencil_attachment: None,
354                occlusion_query_set: None,
355                timestamp_writes: None,
356            });
357
358            render_pass.set_pipeline(&self.render_pipeline);
359
360            // Render all sprites
361            for (vertex_buffer, index_buffer) in &buffers {
362                render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
363                render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
364                render_pass.draw_indexed(0..6, 0, 0..1);
365            }
366        }
367
368        self.queue.submit(std::iter::once(encoder.finish()));
369        output.present();
370
371        Ok(())
372    }
373
374    fn create_sprite_vertices(&self, transform: &Transform, sprite: &Sprite) -> Vec<Vertex> {
375        let half_width = sprite.size.x / 2.0;
376        let half_height = sprite.size.y / 2.0;
377
378        let _aspect = self.size.width as f32 / self.size.height as f32;
379
380        let x = transform.position.x / (self.size.width as f32 / 2.0);
381        let y = -transform.position.y / (self.size.height as f32 / 2.0);
382        let w = half_width / (self.size.width as f32 / 2.0);
383        let h = half_height / (self.size.height as f32 / 2.0);
384
385        let color = [sprite.color.r, sprite.color.g, sprite.color.b, sprite.color.a];
386
387        vec![
388            Vertex {
389                position: [x - w, y - h],
390                color,
391            },
392            Vertex {
393                position: [x + w, y - h],
394                color,
395            },
396            Vertex {
397                position: [x + w, y + h],
398                color,
399            },
400            Vertex {
401                position: [x - w, y + h],
402                color,
403            },
404        ]
405    }
406}
407
408#[derive(Clone, Debug)]
409pub struct Sprite {
410    pub size: Vec2,
411    pub color: Color,
412}
413
414impl Sprite {
415    pub fn new(width: f32, height: f32) -> Self {
416        Self {
417            size: Vec2::new(width, height),
418            color: Color::WHITE,
419        }
420    }
421
422    pub fn with_color(mut self, color: Color) -> Self {
423        self.color = color;
424        self
425    }
426}
427
428impl crate::ecs::Component for Sprite {}