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
55 .context
56 .device()
57 .create_buffer(&wgpu::BufferDescriptor {
58 label,
59 size: std::mem::size_of_val(data) as u64,
60 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
61 mapped_at_creation: false,
62 });
63
64 self.context
65 .queue()
66 .write_buffer(&buffer, 0, bytemuck::cast_slice(data));
67
68 buffer
69 }
70
71 pub fn create_index_buffer<T: bytemuck::Pod>(
73 &self,
74 label: Option<&str>,
75 data: &[T],
76 ) -> wgpu::Buffer {
77 profile_function!();
78 let buffer = self
79 .context
80 .device()
81 .create_buffer(&wgpu::BufferDescriptor {
82 label,
83 size: std::mem::size_of_val(data) as u64,
84 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
85 mapped_at_creation: false,
86 });
87
88 self.context
89 .queue()
90 .write_buffer(&buffer, 0, bytemuck::cast_slice(data));
91
92 buffer
93 }
94
95 pub fn create_uniform_buffer<T: bytemuck::Pod>(
97 &self,
98 label: Option<&str>,
99 data: &T,
100 ) -> wgpu::Buffer {
101 profile_function!();
102 let buffer = self
103 .context
104 .device()
105 .create_buffer(&wgpu::BufferDescriptor {
106 label,
107 size: std::mem::size_of::<T>() as u64,
108 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
109 mapped_at_creation: false,
110 });
111
112 self.context.queue().write_buffer(
113 &buffer,
114 0,
115 bytemuck::cast_slice(std::slice::from_ref(data)),
116 );
117
118 buffer
119 }
120
121 pub fn update_uniform_buffer<T: bytemuck::Pod>(&self, buffer: &wgpu::Buffer, data: &T) {
123 self.context.queue().write_buffer(
124 buffer,
125 0,
126 bytemuck::cast_slice(std::slice::from_ref(data)),
127 );
128 }
129
130 pub fn create_storage_buffer(
139 &self,
140 label: Option<&str>,
141 size: u64,
142 read_only: bool,
143 ) -> wgpu::Buffer {
144 profile_function!();
145 let usage = if read_only {
146 wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
147 } else {
148 wgpu::BufferUsages::STORAGE
149 | wgpu::BufferUsages::COPY_DST
150 | wgpu::BufferUsages::COPY_SRC
151 };
152
153 self.context
154 .device()
155 .create_buffer(&wgpu::BufferDescriptor {
156 label,
157 size,
158 usage,
159 mapped_at_creation: false,
160 })
161 }
162
163 pub fn create_storage_buffer_init<T: bytemuck::Pod>(
172 &self,
173 label: Option<&str>,
174 data: &[T],
175 read_only: bool,
176 ) -> wgpu::Buffer {
177 let usage = if read_only {
178 wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
179 } else {
180 wgpu::BufferUsages::STORAGE
181 | wgpu::BufferUsages::COPY_DST
182 | wgpu::BufferUsages::COPY_SRC
183 };
184
185 let buffer = self
186 .context
187 .device()
188 .create_buffer(&wgpu::BufferDescriptor {
189 label,
190 size: std::mem::size_of_val(data) as u64,
191 usage,
192 mapped_at_creation: false,
193 });
194
195 self.context
196 .queue()
197 .write_buffer(&buffer, 0, bytemuck::cast_slice(data));
198
199 buffer
200 }
201
202 pub fn update_storage_buffer<T: bytemuck::Pod>(
210 &self,
211 buffer: &wgpu::Buffer,
212 offset: u64,
213 data: &[T],
214 ) {
215 self.context
216 .queue()
217 .write_buffer(buffer, offset, bytemuck::cast_slice(data));
218 }
219
220 pub fn create_texture(&self, descriptor: &wgpu::TextureDescriptor) -> wgpu::Texture {
222 self.context.device().create_texture(descriptor)
223 }
224
225 pub fn create_texture_2d(
227 &self,
228 label: Option<&str>,
229 width: u32,
230 height: u32,
231 format: wgpu::TextureFormat,
232 usage: wgpu::TextureUsages,
233 data: &[u8],
234 ) -> wgpu::Texture {
235 let size = wgpu::Extent3d {
236 width,
237 height,
238 depth_or_array_layers: 1,
239 };
240
241 let texture = self
242 .context
243 .device()
244 .create_texture(&wgpu::TextureDescriptor {
245 label,
246 size,
247 mip_level_count: 1,
248 sample_count: 1,
249 dimension: wgpu::TextureDimension::D2,
250 format,
251 usage: usage | wgpu::TextureUsages::COPY_DST,
252 view_formats: &[],
253 });
254
255 let bytes_per_pixel = format.block_copy_size(None).unwrap();
256
257 self.context.queue().write_texture(
258 wgpu::TexelCopyTextureInfo {
259 texture: &texture,
260 mip_level: 0,
261 origin: wgpu::Origin3d::ZERO,
262 aspect: wgpu::TextureAspect::All,
263 },
264 data,
265 wgpu::TexelCopyBufferLayout {
266 offset: 0,
267 bytes_per_row: Some(width * bytes_per_pixel),
268 rows_per_image: Some(height),
269 },
270 size,
271 );
272
273 texture
274 }
275
276 pub fn create_sampler(&self, descriptor: &wgpu::SamplerDescriptor) -> wgpu::Sampler {
278 self.context.device().create_sampler(descriptor)
279 }
280
281 pub fn create_linear_sampler(&self, label: Option<&str>) -> wgpu::Sampler {
283 self.context
284 .device()
285 .create_sampler(&wgpu::SamplerDescriptor {
286 label,
287 address_mode_u: wgpu::AddressMode::ClampToEdge,
288 address_mode_v: wgpu::AddressMode::ClampToEdge,
289 address_mode_w: wgpu::AddressMode::ClampToEdge,
290 mag_filter: wgpu::FilterMode::Linear,
291 min_filter: wgpu::FilterMode::Linear,
292 mipmap_filter: wgpu::FilterMode::Nearest,
293 ..Default::default()
294 })
295 }
296
297 pub fn create_nearest_sampler(&self, label: Option<&str>) -> wgpu::Sampler {
299 self.context
300 .device()
301 .create_sampler(&wgpu::SamplerDescriptor {
302 label,
303 address_mode_u: wgpu::AddressMode::ClampToEdge,
304 address_mode_v: wgpu::AddressMode::ClampToEdge,
305 address_mode_w: wgpu::AddressMode::ClampToEdge,
306 mag_filter: wgpu::FilterMode::Nearest,
307 min_filter: wgpu::FilterMode::Nearest,
308 mipmap_filter: wgpu::FilterMode::Nearest,
309 ..Default::default()
310 })
311 }
312
313 pub fn create_bind_group_layout(
315 &self,
316 label: Option<&str>,
317 entries: &[wgpu::BindGroupLayoutEntry],
318 ) -> wgpu::BindGroupLayout {
319 profile_function!();
320 self.context
321 .device()
322 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label, entries })
323 }
324
325 pub fn create_bind_group(
327 &self,
328 label: Option<&str>,
329 layout: &wgpu::BindGroupLayout,
330 entries: &[wgpu::BindGroupEntry],
331 ) -> wgpu::BindGroup {
332 profile_function!();
333 self.context
334 .device()
335 .create_bind_group(&wgpu::BindGroupDescriptor {
336 label,
337 layout,
338 entries,
339 })
340 }
341
342 pub fn create_pipeline_layout(
344 &self,
345 label: Option<&str>,
346 bind_group_layouts: &[&wgpu::BindGroupLayout],
347 push_constant_ranges: &[wgpu::PushConstantRange],
348 ) -> wgpu::PipelineLayout {
349 profile_function!();
350 self.context
351 .device()
352 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
353 label,
354 bind_group_layouts,
355 push_constant_ranges,
356 })
357 }
358
359 pub fn create_render_pipeline(
361 &self,
362 descriptor: &wgpu::RenderPipelineDescriptor,
363 ) -> wgpu::RenderPipeline {
364 profile_function!();
365 self.context.device().create_render_pipeline(descriptor)
366 }
367
368 pub fn create_compute_pipeline(
370 &self,
371 descriptor: &wgpu::ComputePipelineDescriptor,
372 ) -> wgpu::ComputePipeline {
373 profile_function!();
374 self.context.device().create_compute_pipeline(descriptor)
375 }
376
377 pub fn create_command_encoder(&self, label: Option<&str>) -> wgpu::CommandEncoder {
379 self.context
380 .device()
381 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label })
382 }
383
384 pub fn submit<I>(&self, command_buffers: I)
386 where
387 I: IntoIterator<Item = wgpu::CommandBuffer>,
388 {
389 self.context.queue().submit(command_buffers);
390 }
391
392 pub fn create_typed_vertex_buffer<T: bytemuck::Pod>(
400 &self,
401 label: Option<&str>,
402 data: &[T],
403 ) -> TypedBuffer<T> {
404 TypedBuffer::new(
405 self.context.device(),
406 label,
407 data,
408 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
409 )
410 }
411
412 pub fn create_typed_index_buffer<T: bytemuck::Pod>(
416 &self,
417 label: Option<&str>,
418 data: &[T],
419 ) -> TypedBuffer<T> {
420 TypedBuffer::new(
421 self.context.device(),
422 label,
423 data,
424 wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
425 )
426 }
427
428 pub fn create_typed_uniform<T: bytemuck::Pod>(
432 &self,
433 label: Option<&str>,
434 data: &T,
435 ) -> UniformBuffer<T> {
436 UniformBuffer::new_uniform(self.context.device(), label, data)
437 }
438
439 pub fn create_gpu_texture_2d(
443 &self,
444 label: Option<&str>,
445 width: u32,
446 height: u32,
447 format: wgpu::TextureFormat,
448 usage: wgpu::TextureUsages,
449 ) -> GpuTexture {
450 GpuTexture::new_2d(self.context.device(), label, width, height, format, usage)
451 }
452
453 pub fn create_gpu_texture_from_data(
457 &self,
458 label: Option<&str>,
459 width: u32,
460 height: u32,
461 format: wgpu::TextureFormat,
462 data: &[u8],
463 ) -> GpuTexture {
464 profile_function!();
465 GpuTexture::from_data(
466 self.context.device(),
467 self.context.queue(),
468 label,
469 width,
470 height,
471 format,
472 data,
473 )
474 }
475}
476
477pub struct RenderPipelineBuilder<'a> {
495 renderer: &'a Renderer,
496 label: Option<&'a str>,
497 shader: Option<&'a wgpu::ShaderModule>,
498 vertex_entry: &'a str,
499 fragment_entry: &'a str,
500 layout: Option<&'a wgpu::PipelineLayout>,
501 vertex_buffers: Vec<wgpu::VertexBufferLayout<'a>>,
502 color_targets: Vec<Option<wgpu::ColorTargetState>>,
503 primitive: wgpu::PrimitiveState,
504 depth_stencil: Option<wgpu::DepthStencilState>,
505 multisample: wgpu::MultisampleState,
506}
507
508impl<'a> RenderPipelineBuilder<'a> {
509 pub fn new(renderer: &'a Renderer) -> Self {
511 Self {
512 renderer,
513 label: None,
514 shader: None,
515 vertex_entry: "vs_main",
516 fragment_entry: "fs_main",
517 layout: None,
518 vertex_buffers: Vec::new(),
519 color_targets: Vec::new(),
520 primitive: wgpu::PrimitiveState {
521 topology: wgpu::PrimitiveTopology::TriangleList,
522 strip_index_format: None,
523 front_face: wgpu::FrontFace::Ccw,
524 cull_mode: Some(wgpu::Face::Back),
525 polygon_mode: wgpu::PolygonMode::Fill,
526 unclipped_depth: false,
527 conservative: false,
528 },
529 depth_stencil: None,
530 multisample: wgpu::MultisampleState {
531 count: 1,
532 mask: !0,
533 alpha_to_coverage_enabled: false,
534 },
535 }
536 }
537
538 pub fn label(mut self, label: &'a str) -> Self {
540 self.label = Some(label);
541 self
542 }
543
544 pub fn shader(mut self, shader: &'a wgpu::ShaderModule) -> Self {
546 self.shader = Some(shader);
547 self
548 }
549
550 pub fn vertex_entry(mut self, entry: &'a str) -> Self {
552 self.vertex_entry = entry;
553 self
554 }
555
556 pub fn fragment_entry(mut self, entry: &'a str) -> Self {
558 self.fragment_entry = entry;
559 self
560 }
561
562 pub fn layout(mut self, layout: &'a wgpu::PipelineLayout) -> Self {
564 self.layout = Some(layout);
565 self
566 }
567
568 pub fn vertex_buffer(mut self, layout: wgpu::VertexBufferLayout<'a>) -> Self {
570 self.vertex_buffers.push(layout);
571 self
572 }
573
574 pub fn color_target(mut self, target: wgpu::ColorTargetState) -> Self {
576 self.color_targets.push(Some(target));
577 self
578 }
579
580 pub fn primitive(mut self, primitive: wgpu::PrimitiveState) -> Self {
582 self.primitive = primitive;
583 self
584 }
585
586 pub fn depth_stencil(mut self, depth_stencil: wgpu::DepthStencilState) -> Self {
588 self.depth_stencil = Some(depth_stencil);
589 self
590 }
591
592 pub fn multisample(mut self, multisample: wgpu::MultisampleState) -> Self {
594 self.multisample = multisample;
595 self
596 }
597
598 pub fn build(self) -> wgpu::RenderPipeline {
604 let shader = self.shader.expect("Shader module is required");
605 let layout = self.layout.expect("Pipeline layout is required");
606
607 self.renderer
608 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
609 label: self.label,
610 layout: Some(layout),
611 vertex: wgpu::VertexState {
612 module: shader,
613 entry_point: Some(self.vertex_entry),
614 buffers: &self.vertex_buffers,
615 compilation_options: wgpu::PipelineCompilationOptions::default(),
616 },
617 fragment: Some(wgpu::FragmentState {
618 module: shader,
619 entry_point: Some(self.fragment_entry),
620 targets: &self.color_targets,
621 compilation_options: wgpu::PipelineCompilationOptions::default(),
622 }),
623 primitive: self.primitive,
624 depth_stencil: self.depth_stencil,
625 multisample: self.multisample,
626 multiview: None,
627 cache: None,
628 })
629 }
630}
631
632pub struct ComputePipelineBuilder<'a> {
645 renderer: &'a Renderer,
646 label: Option<&'a str>,
647 shader: Option<&'a wgpu::ShaderModule>,
648 entry: &'a str,
649 layout: Option<&'a wgpu::PipelineLayout>,
650}
651
652impl<'a> ComputePipelineBuilder<'a> {
653 pub fn new(renderer: &'a Renderer) -> Self {
655 Self {
656 renderer,
657 label: None,
658 shader: None,
659 entry: "main",
660 layout: None,
661 }
662 }
663
664 pub fn label(mut self, label: &'a str) -> Self {
666 self.label = Some(label);
667 self
668 }
669
670 pub fn shader(mut self, shader: &'a wgpu::ShaderModule) -> Self {
672 self.shader = Some(shader);
673 self
674 }
675
676 pub fn entry(mut self, entry: &'a str) -> Self {
680 self.entry = entry;
681 self
682 }
683
684 pub fn layout(mut self, layout: &'a wgpu::PipelineLayout) -> Self {
686 self.layout = Some(layout);
687 self
688 }
689
690 pub fn build(self) -> wgpu::ComputePipeline {
696 let shader = self.shader.expect("Shader module is required");
697 let layout = self.layout.expect("Pipeline layout is required");
698
699 self.renderer
700 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
701 label: self.label,
702 layout: Some(layout),
703 module: shader,
704 entry_point: Some(self.entry),
705 compilation_options: wgpu::PipelineCompilationOptions::default(),
706 cache: None,
707 })
708 }
709}