1use astrelis_core::profiling::profile_function;
2
3use crate::context::GraphicsContext;
4use crate::types::{GpuTexture, TypedBuffer, UniformBuffer};
5use std::sync::Arc;
6
7pub struct Renderer {
12 context: Arc<GraphicsContext>,
13}
14
15impl Renderer {
16 pub fn new(context: Arc<GraphicsContext>) -> Self {
18 Self { context }
19 }
20
21 pub fn context(&self) -> &GraphicsContext {
23 &self.context
24 }
25
26 pub fn device(&self) -> &wgpu::Device {
28 self.context.device()
29 }
30
31 pub fn queue(&self) -> &wgpu::Queue {
33 self.context.queue()
34 }
35
36 pub fn create_shader(&self, label: Option<&str>, source: &str) -> wgpu::ShaderModule {
38 profile_function!();
39 self.context
40 .device()
41 .create_shader_module(wgpu::ShaderModuleDescriptor {
42 label,
43 source: wgpu::ShaderSource::Wgsl(source.into()),
44 })
45 }
46
47 pub fn create_vertex_buffer<T: bytemuck::Pod>(
49 &self,
50 label: Option<&str>,
51 data: &[T],
52 ) -> wgpu::Buffer {
53 profile_function!();
54 let buffer = self.context.device().create_buffer(&wgpu::BufferDescriptor {
55 label,
56 size: std::mem::size_of_val(data) as u64,
57 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
58 mapped_at_creation: false,
59 });
60
61 self.context
62 .queue()
63 .write_buffer(&buffer, 0, bytemuck::cast_slice(data));
64
65 buffer
66 }
67
68 pub fn create_index_buffer<T: bytemuck::Pod>(
70 &self,
71 label: Option<&str>,
72 data: &[T],
73 ) -> wgpu::Buffer {
74 profile_function!();
75 let buffer = self.context.device().create_buffer(&wgpu::BufferDescriptor {
76 label,
77 size: std::mem::size_of_val(data) as u64,
78 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
79 mapped_at_creation: false,
80 });
81
82 self.context
83 .queue()
84 .write_buffer(&buffer, 0, bytemuck::cast_slice(data));
85
86 buffer
87 }
88
89 pub fn create_uniform_buffer<T: bytemuck::Pod>(
91 &self,
92 label: Option<&str>,
93 data: &T,
94 ) -> wgpu::Buffer {
95 profile_function!();
96 let buffer = self.context.device().create_buffer(&wgpu::BufferDescriptor {
97 label,
98 size: std::mem::size_of::<T>() as u64,
99 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
100 mapped_at_creation: false,
101 });
102
103 self.context.queue().write_buffer(
104 &buffer,
105 0,
106 bytemuck::cast_slice(std::slice::from_ref(data)),
107 );
108
109 buffer
110 }
111
112 pub fn update_uniform_buffer<T: bytemuck::Pod>(&self, buffer: &wgpu::Buffer, data: &T) {
114 self.context.queue().write_buffer(
115 buffer,
116 0,
117 bytemuck::cast_slice(std::slice::from_ref(data)),
118 );
119 }
120
121 pub fn create_storage_buffer(
130 &self,
131 label: Option<&str>,
132 size: u64,
133 read_only: bool,
134 ) -> wgpu::Buffer {
135 profile_function!();
136 let usage = if read_only {
137 wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
138 } else {
139 wgpu::BufferUsages::STORAGE
140 | wgpu::BufferUsages::COPY_DST
141 | wgpu::BufferUsages::COPY_SRC
142 };
143
144 self.context.device().create_buffer(&wgpu::BufferDescriptor {
145 label,
146 size,
147 usage,
148 mapped_at_creation: false,
149 })
150 }
151
152 pub fn create_storage_buffer_init<T: bytemuck::Pod>(
161 &self,
162 label: Option<&str>,
163 data: &[T],
164 read_only: bool,
165 ) -> wgpu::Buffer {
166 let usage = if read_only {
167 wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
168 } else {
169 wgpu::BufferUsages::STORAGE
170 | wgpu::BufferUsages::COPY_DST
171 | wgpu::BufferUsages::COPY_SRC
172 };
173
174 let buffer = self.context.device().create_buffer(&wgpu::BufferDescriptor {
175 label,
176 size: std::mem::size_of_val(data) as u64,
177 usage,
178 mapped_at_creation: false,
179 });
180
181 self.context
182 .queue()
183 .write_buffer(&buffer, 0, bytemuck::cast_slice(data));
184
185 buffer
186 }
187
188 pub fn update_storage_buffer<T: bytemuck::Pod>(
196 &self,
197 buffer: &wgpu::Buffer,
198 offset: u64,
199 data: &[T],
200 ) {
201 self.context
202 .queue()
203 .write_buffer(buffer, offset, bytemuck::cast_slice(data));
204 }
205
206 pub fn create_texture(&self, descriptor: &wgpu::TextureDescriptor) -> wgpu::Texture {
208 self.context.device().create_texture(descriptor)
209 }
210
211 pub fn create_texture_2d(
213 &self,
214 label: Option<&str>,
215 width: u32,
216 height: u32,
217 format: wgpu::TextureFormat,
218 usage: wgpu::TextureUsages,
219 data: &[u8],
220 ) -> wgpu::Texture {
221 let size = wgpu::Extent3d {
222 width,
223 height,
224 depth_or_array_layers: 1,
225 };
226
227 let texture = self
228 .context
229 .device()
230 .create_texture(&wgpu::TextureDescriptor {
231 label,
232 size,
233 mip_level_count: 1,
234 sample_count: 1,
235 dimension: wgpu::TextureDimension::D2,
236 format,
237 usage: usage | wgpu::TextureUsages::COPY_DST,
238 view_formats: &[],
239 });
240
241 let bytes_per_pixel = format.block_copy_size(None).unwrap();
242
243 self.context.queue().write_texture(
244 wgpu::TexelCopyTextureInfo {
245 texture: &texture,
246 mip_level: 0,
247 origin: wgpu::Origin3d::ZERO,
248 aspect: wgpu::TextureAspect::All,
249 },
250 data,
251 wgpu::TexelCopyBufferLayout {
252 offset: 0,
253 bytes_per_row: Some(width * bytes_per_pixel),
254 rows_per_image: Some(height),
255 },
256 size,
257 );
258
259 texture
260 }
261
262 pub fn create_sampler(&self, descriptor: &wgpu::SamplerDescriptor) -> wgpu::Sampler {
264 self.context.device().create_sampler(descriptor)
265 }
266
267 pub fn create_linear_sampler(&self, label: Option<&str>) -> wgpu::Sampler {
269 self.context
270 .device()
271 .create_sampler(&wgpu::SamplerDescriptor {
272 label,
273 address_mode_u: wgpu::AddressMode::ClampToEdge,
274 address_mode_v: wgpu::AddressMode::ClampToEdge,
275 address_mode_w: wgpu::AddressMode::ClampToEdge,
276 mag_filter: wgpu::FilterMode::Linear,
277 min_filter: wgpu::FilterMode::Linear,
278 mipmap_filter: wgpu::FilterMode::Nearest,
279 ..Default::default()
280 })
281 }
282
283 pub fn create_nearest_sampler(&self, label: Option<&str>) -> wgpu::Sampler {
285 self.context
286 .device()
287 .create_sampler(&wgpu::SamplerDescriptor {
288 label,
289 address_mode_u: wgpu::AddressMode::ClampToEdge,
290 address_mode_v: wgpu::AddressMode::ClampToEdge,
291 address_mode_w: wgpu::AddressMode::ClampToEdge,
292 mag_filter: wgpu::FilterMode::Nearest,
293 min_filter: wgpu::FilterMode::Nearest,
294 mipmap_filter: wgpu::FilterMode::Nearest,
295 ..Default::default()
296 })
297 }
298
299 pub fn create_bind_group_layout(
301 &self,
302 label: Option<&str>,
303 entries: &[wgpu::BindGroupLayoutEntry],
304 ) -> wgpu::BindGroupLayout {
305 profile_function!();
306 self.context
307 .device()
308 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label, entries })
309 }
310
311 pub fn create_bind_group(
313 &self,
314 label: Option<&str>,
315 layout: &wgpu::BindGroupLayout,
316 entries: &[wgpu::BindGroupEntry],
317 ) -> wgpu::BindGroup {
318 profile_function!();
319 self.context
320 .device()
321 .create_bind_group(&wgpu::BindGroupDescriptor {
322 label,
323 layout,
324 entries,
325 })
326 }
327
328 pub fn create_pipeline_layout(
330 &self,
331 label: Option<&str>,
332 bind_group_layouts: &[&wgpu::BindGroupLayout],
333 push_constant_ranges: &[wgpu::PushConstantRange],
334 ) -> wgpu::PipelineLayout {
335 profile_function!();
336 self.context
337 .device()
338 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
339 label,
340 bind_group_layouts,
341 push_constant_ranges,
342 })
343 }
344
345 pub fn create_render_pipeline(
347 &self,
348 descriptor: &wgpu::RenderPipelineDescriptor,
349 ) -> wgpu::RenderPipeline {
350 profile_function!();
351 self.context.device().create_render_pipeline(descriptor)
352 }
353
354 pub fn create_compute_pipeline(
356 &self,
357 descriptor: &wgpu::ComputePipelineDescriptor,
358 ) -> wgpu::ComputePipeline {
359 profile_function!();
360 self.context.device().create_compute_pipeline(descriptor)
361 }
362
363 pub fn create_command_encoder(&self, label: Option<&str>) -> wgpu::CommandEncoder {
365 self.context
366 .device()
367 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label })
368 }
369
370 pub fn submit<I>(&self, command_buffers: I)
372 where
373 I: IntoIterator<Item = wgpu::CommandBuffer>,
374 {
375 self.context.queue().submit(command_buffers);
376 }
377
378 pub fn create_typed_vertex_buffer<T: bytemuck::Pod>(
386 &self,
387 label: Option<&str>,
388 data: &[T],
389 ) -> TypedBuffer<T> {
390 TypedBuffer::new(
391 self.context.device(),
392 label,
393 data,
394 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
395 )
396 }
397
398 pub fn create_typed_index_buffer<T: bytemuck::Pod>(
402 &self,
403 label: Option<&str>,
404 data: &[T],
405 ) -> TypedBuffer<T> {
406 TypedBuffer::new(
407 self.context.device(),
408 label,
409 data,
410 wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
411 )
412 }
413
414 pub fn create_typed_uniform<T: bytemuck::Pod>(
418 &self,
419 label: Option<&str>,
420 data: &T,
421 ) -> UniformBuffer<T> {
422 UniformBuffer::new_uniform(self.context.device(), label, data)
423 }
424
425 pub fn create_gpu_texture_2d(
429 &self,
430 label: Option<&str>,
431 width: u32,
432 height: u32,
433 format: wgpu::TextureFormat,
434 usage: wgpu::TextureUsages,
435 ) -> GpuTexture {
436 GpuTexture::new_2d(self.context.device(), label, width, height, format, usage)
437 }
438
439 pub fn create_gpu_texture_from_data(
443 &self,
444 label: Option<&str>,
445 width: u32,
446 height: u32,
447 format: wgpu::TextureFormat,
448 data: &[u8],
449 ) -> GpuTexture {
450 profile_function!();
451 GpuTexture::from_data(
452 self.context.device(),
453 self.context.queue(),
454 label,
455 width,
456 height,
457 format,
458 data,
459 )
460 }
461}
462
463pub struct RenderPipelineBuilder<'a> {
481 renderer: &'a Renderer,
482 label: Option<&'a str>,
483 shader: Option<&'a wgpu::ShaderModule>,
484 vertex_entry: &'a str,
485 fragment_entry: &'a str,
486 layout: Option<&'a wgpu::PipelineLayout>,
487 vertex_buffers: Vec<wgpu::VertexBufferLayout<'a>>,
488 color_targets: Vec<Option<wgpu::ColorTargetState>>,
489 primitive: wgpu::PrimitiveState,
490 depth_stencil: Option<wgpu::DepthStencilState>,
491 multisample: wgpu::MultisampleState,
492}
493
494impl<'a> RenderPipelineBuilder<'a> {
495 pub fn new(renderer: &'a Renderer) -> Self {
497 Self {
498 renderer,
499 label: None,
500 shader: None,
501 vertex_entry: "vs_main",
502 fragment_entry: "fs_main",
503 layout: None,
504 vertex_buffers: Vec::new(),
505 color_targets: Vec::new(),
506 primitive: wgpu::PrimitiveState {
507 topology: wgpu::PrimitiveTopology::TriangleList,
508 strip_index_format: None,
509 front_face: wgpu::FrontFace::Ccw,
510 cull_mode: Some(wgpu::Face::Back),
511 polygon_mode: wgpu::PolygonMode::Fill,
512 unclipped_depth: false,
513 conservative: false,
514 },
515 depth_stencil: None,
516 multisample: wgpu::MultisampleState {
517 count: 1,
518 mask: !0,
519 alpha_to_coverage_enabled: false,
520 },
521 }
522 }
523
524 pub fn label(mut self, label: &'a str) -> Self {
526 self.label = Some(label);
527 self
528 }
529
530 pub fn shader(mut self, shader: &'a wgpu::ShaderModule) -> Self {
532 self.shader = Some(shader);
533 self
534 }
535
536 pub fn vertex_entry(mut self, entry: &'a str) -> Self {
538 self.vertex_entry = entry;
539 self
540 }
541
542 pub fn fragment_entry(mut self, entry: &'a str) -> Self {
544 self.fragment_entry = entry;
545 self
546 }
547
548 pub fn layout(mut self, layout: &'a wgpu::PipelineLayout) -> Self {
550 self.layout = Some(layout);
551 self
552 }
553
554 pub fn vertex_buffer(mut self, layout: wgpu::VertexBufferLayout<'a>) -> Self {
556 self.vertex_buffers.push(layout);
557 self
558 }
559
560 pub fn color_target(mut self, target: wgpu::ColorTargetState) -> Self {
562 self.color_targets.push(Some(target));
563 self
564 }
565
566 pub fn primitive(mut self, primitive: wgpu::PrimitiveState) -> Self {
568 self.primitive = primitive;
569 self
570 }
571
572 pub fn depth_stencil(mut self, depth_stencil: wgpu::DepthStencilState) -> Self {
574 self.depth_stencil = Some(depth_stencil);
575 self
576 }
577
578 pub fn multisample(mut self, multisample: wgpu::MultisampleState) -> Self {
580 self.multisample = multisample;
581 self
582 }
583
584 pub fn build(self) -> wgpu::RenderPipeline {
590 let shader = self.shader.expect("Shader module is required");
591 let layout = self.layout.expect("Pipeline layout is required");
592
593 self.renderer
594 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
595 label: self.label,
596 layout: Some(layout),
597 vertex: wgpu::VertexState {
598 module: shader,
599 entry_point: Some(self.vertex_entry),
600 buffers: &self.vertex_buffers,
601 compilation_options: wgpu::PipelineCompilationOptions::default(),
602 },
603 fragment: Some(wgpu::FragmentState {
604 module: shader,
605 entry_point: Some(self.fragment_entry),
606 targets: &self.color_targets,
607 compilation_options: wgpu::PipelineCompilationOptions::default(),
608 }),
609 primitive: self.primitive,
610 depth_stencil: self.depth_stencil,
611 multisample: self.multisample,
612 multiview: None,
613 cache: None,
614 })
615 }
616}
617
618pub struct ComputePipelineBuilder<'a> {
631 renderer: &'a Renderer,
632 label: Option<&'a str>,
633 shader: Option<&'a wgpu::ShaderModule>,
634 entry: &'a str,
635 layout: Option<&'a wgpu::PipelineLayout>,
636}
637
638impl<'a> ComputePipelineBuilder<'a> {
639 pub fn new(renderer: &'a Renderer) -> Self {
641 Self {
642 renderer,
643 label: None,
644 shader: None,
645 entry: "main",
646 layout: None,
647 }
648 }
649
650 pub fn label(mut self, label: &'a str) -> Self {
652 self.label = Some(label);
653 self
654 }
655
656 pub fn shader(mut self, shader: &'a wgpu::ShaderModule) -> Self {
658 self.shader = Some(shader);
659 self
660 }
661
662 pub fn entry(mut self, entry: &'a str) -> Self {
666 self.entry = entry;
667 self
668 }
669
670 pub fn layout(mut self, layout: &'a wgpu::PipelineLayout) -> Self {
672 self.layout = Some(layout);
673 self
674 }
675
676 pub fn build(self) -> wgpu::ComputePipeline {
682 let shader = self.shader.expect("Shader module is required");
683 let layout = self.layout.expect("Pipeline layout is required");
684
685 self.renderer
686 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
687 label: self.label,
688 layout: Some(layout),
689 module: shader,
690 entry_point: Some(self.entry),
691 compilation_options: wgpu::PipelineCompilationOptions::default(),
692 cache: None,
693 })
694 }
695}