renderer.rs

  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}