1use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
2
3use super::{Scene, Size};
4
5pub struct Renderer {
6 device: wgpu::Device,
7 queue: wgpu::Queue,
8 surface: wgpu::Surface,
9 surface_config: wgpu::SurfaceConfiguration,
10 pipeline: wgpu::RenderPipeline,
11 vertex_buffer: wgpu::Buffer,
12 vertex_count: u32,
13}
14
15pub trait Window: HasRawWindowHandle + HasRawDisplayHandle {
16 fn inner_size(&self) -> Size<u32>;
17}
18
19impl Renderer {
20 pub async fn new<W>(window: &W) -> Self
21 where
22 W: Window,
23 {
24 let instance = wgpu::Instance::new(Default::default());
25 let surface = unsafe { instance.create_surface(window).unwrap() };
26
27 let adapter = instance
28 .request_adapter(&wgpu::RequestAdapterOptions::default())
29 .await
30 .unwrap();
31
32 let (device, queue) = adapter
33 .request_device(&wgpu::DeviceDescriptor::default(), None)
34 .await
35 .unwrap();
36
37 let surface_config = wgpu::SurfaceConfiguration {
38 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
39 format: wgpu::TextureFormat::Bgra8UnormSrgb,
40 width: window.inner_size().width,
41 height: window.inner_size().height,
42
43 // "FIFO" mode renders frames in queue synced with the display's refresh rate.
44 // Avoids screen tearing but may not offer the lowest latency. Ideal when image
45 // quality takes priority over input latency.
46 present_mode: wgpu::PresentMode::Fifo,
47
48 // Use the Premultiplied alpha mode. With premultiplication, the color components
49 // are multiplied by the alpha value before storage or blending, meaning calculations
50 // with colors already factor in the influence of alpha. This typically results
51 // in better performance and avoids a separate multiplication operation during blending.
52 alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
53
54 // Specify the color formats for the views the surface can have.
55 // In this case, the format is BGRA (blue, green, red, alpha) with unsigned
56 // normalised integers in the 8-bit range and the color space is sRGB (standard RGB).
57 // sRGB is the standard color space for displaying images and video on digital displays,
58 // as it optimises color accuracy and consistency.
59 view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb],
60 };
61
62 surface.configure(&device, &surface_config);
63
64 let vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
65 label: Some("Vertex Shader"),
66 source: wgpu::ShaderSource::Wgsl(include_str!("shader.vert.wgsl").into()),
67 });
68
69 let fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
70 label: Some("Fragment Shader"),
71 source: wgpu::ShaderSource::Wgsl(include_str!("shader.frag.wgsl").into()),
72 });
73
74 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
75 label: Some("Render Pipeline Layout"),
76 bind_group_layouts: &[],
77 push_constant_ranges: &[],
78 });
79
80 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
81 label: Some("Vertex Buffer"),
82 size: 0,
83 usage: wgpu::BufferUsages::VERTEX,
84 mapped_at_creation: false,
85 });
86
87 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
88 label: Some("Render Pipeline"),
89 layout: Some(&pipeline_layout),
90 vertex: wgpu::VertexState {
91 module: &vs_module,
92 entry_point: "main",
93 buffers: &[],
94 },
95 fragment: Some(wgpu::FragmentState {
96 module: &fs_module,
97 entry_point: "main",
98 targets: &[Some(wgpu::ColorTargetState {
99 format: surface_config.format,
100 blend: Some(wgpu::BlendState::REPLACE),
101 write_mask: wgpu::ColorWrites::ALL,
102 })],
103 }),
104 primitive: wgpu::PrimitiveState {
105 topology: wgpu::PrimitiveTopology::TriangleStrip,
106 ..Default::default()
107 },
108 depth_stencil: None,
109 multisample: wgpu::MultisampleState::default(),
110 multiview: None,
111 });
112
113 Self {
114 device,
115 queue,
116 surface,
117 surface_config,
118 pipeline,
119 vertex_buffer,
120 vertex_count: 0,
121 }
122 }
123
124 pub fn render(&mut self, scene: &Scene) {
125 let frame = self.surface.get_current_texture().unwrap();
126 let view = frame
127 .texture
128 .create_view(&wgpu::TextureViewDescriptor::default());
129
130 self.queue.write_buffer(
131 &self.vertex_buffer,
132 0,
133 bytemuck::cast_slice(&scene.opaque_primitives().quads),
134 );
135 self.vertex_count = scene.opaque_primitives().quads.len() as u32;
136
137 let mut encoder = self
138 .device
139 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
140 label: Some("Render Encoder"),
141 });
142
143 {
144 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
145 label: Some("Render Pass"),
146 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
147 view: &view,
148 resolve_target: None,
149 ops: wgpu::Operations {
150 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
151 store: true,
152 },
153 })],
154 depth_stencil_attachment: None,
155 });
156
157 render_pass.set_pipeline(&self.pipeline);
158 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
159 render_pass.draw(0..self.vertex_count, 0..1);
160 }
161
162 self.queue.submit(std::iter::once(encoder.finish()));
163 }
164}