| 1 | //! Submission through wgpu: Metal, Vulkan or GL, whichever the platform offers. |
| 2 | use super::*; |
| 3 | |
| 4 | /// What a frame is drawn into. |
| 5 | #[cfg(not(any(windows, target_os = "macos", target_arch = "wasm32")))] |
| 6 | pub type Target = wgpu::TextureView; |
| 7 | |
| 8 | /// A picture's texture, as its batches bind it. |
| 9 | pub(super) struct Image(wgpu::BindGroup); |
| 10 | |
| 11 | impl AsRef<Image> for Image { |
| 12 | fn as_ref(&self) -> &Image { |
| 13 | self |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | pub(super) struct Gpu { |
| 18 | device: wgpu::Device, |
| 19 | queue: wgpu::Queue, |
| 20 | layout: wgpu::BindGroupLayout, |
| 21 | /// A pipeline for each of `Blend::STATES`. |
| 22 | pipelines: [wgpu::RenderPipeline; 4], |
| 23 | atlas: (wgpu::Texture, wgpu::BindGroup), |
| 24 | nearest: wgpu::Sampler, |
| 25 | linear: wgpu::Sampler, |
| 26 | vertex_buffer: wgpu::Buffer, |
| 27 | format: wgpu::TextureFormat, |
| 28 | /// Offscreen pictures for groups, the target's size, and how each is sampled. |
| 29 | groups: Vec<(wgpu::Texture, wgpu::BindGroup)>, |
| 30 | } |
| 31 | |
| 32 | impl Renderer { |
| 33 | pub fn new(device: wgpu::Device, queue: wgpu::Queue, format: wgpu::TextureFormat) -> Self { |
| 34 | Self::with_gpu(Gpu::new(device, queue, format)) |
| 35 | } |
| 36 | |
| 37 | #[cfg(not(any(windows, target_os = "macos", target_arch = "wasm32")))] |
| 38 | pub fn device(&self) -> &wgpu::Device { |
| 39 | self.gpu.device() |
| 40 | } |
| 41 | |
| 42 | #[cfg(not(any(windows, target_os = "macos", target_arch = "wasm32")))] |
| 43 | pub fn queue(&self) -> &wgpu::Queue { |
| 44 | self.gpu.queue() |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | #[cfg(not(windows))] |
| 49 | impl Gpu { |
| 50 | pub(super) fn device(&self) -> &wgpu::Device { |
| 51 | &self.device |
| 52 | } |
| 53 | |
| 54 | pub(super) fn queue(&self) -> &wgpu::Queue { |
| 55 | &self.queue |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | impl Gpu { |
| 60 | pub(super) fn new( |
| 61 | device: wgpu::Device, |
| 62 | queue: wgpu::Queue, |
| 63 | format: wgpu::TextureFormat, |
| 64 | ) -> Self { |
| 65 | let shader = device.create_shader_module(wgpu::include_wgsl!("../draw.wgsl")); |
| 66 | let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { |
| 67 | label: Some("Draw texture"), |
| 68 | entries: &[ |
| 69 | wgpu::BindGroupLayoutEntry { |
| 70 | binding: 0, |
| 71 | visibility: wgpu::ShaderStages::FRAGMENT, |
| 72 | ty: wgpu::BindingType::Texture { |
| 73 | sample_type: wgpu::TextureSampleType::Float { filterable: true }, |
| 74 | view_dimension: wgpu::TextureViewDimension::D2, |
| 75 | multisampled: false, |
| 76 | }, |
| 77 | count: None, |
| 78 | }, |
| 79 | wgpu::BindGroupLayoutEntry { |
| 80 | binding: 1, |
| 81 | visibility: wgpu::ShaderStages::FRAGMENT, |
| 82 | ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), |
| 83 | count: None, |
| 84 | }, |
| 85 | ], |
| 86 | }); |
| 87 | let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { |
| 88 | label: Some("Draw"), |
| 89 | bind_group_layouts: &[Some(&layout)], |
| 90 | ..Default::default() |
| 91 | }); |
| 92 | let attributes: [_; 9] = std::array::from_fn(|index| { |
| 93 | let (_, floats, offset) = ATTRIBUTES[index]; |
| 94 | wgpu::VertexAttribute { |
| 95 | format: [ |
| 96 | wgpu::VertexFormat::Float32, |
| 97 | wgpu::VertexFormat::Float32x2, |
| 98 | wgpu::VertexFormat::Float32x3, |
| 99 | wgpu::VertexFormat::Float32x4, |
| 100 | ][floats - 1], |
| 101 | offset: offset as u64, |
| 102 | shader_location: index as u32, |
| 103 | } |
| 104 | }); |
| 105 | let pipelines = Blend::STATES.map(|[color, dst_color, alpha, dst_alpha]| { |
| 106 | device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { |
| 107 | label: Some("Draw"), |
| 108 | layout: Some(&pipeline_layout), |
| 109 | vertex: wgpu::VertexState { |
| 110 | module: &shader, |
| 111 | entry_point: Some("vertex"), |
| 112 | compilation_options: Default::default(), |
| 113 | buffers: &[Some(wgpu::VertexBufferLayout { |
| 114 | array_stride: size_of::<Vertex>() as u64, |
| 115 | step_mode: wgpu::VertexStepMode::Vertex, |
| 116 | attributes: &attributes, |
| 117 | })], |
| 118 | }, |
| 119 | fragment: Some(wgpu::FragmentState { |
| 120 | module: &shader, |
| 121 | entry_point: Some("fragment"), |
| 122 | compilation_options: Default::default(), |
| 123 | targets: &[Some(wgpu::ColorTargetState { |
| 124 | format, |
| 125 | blend: Some(wgpu::BlendState { |
| 126 | color: component([color, dst_color]), |
| 127 | alpha: component([alpha, dst_alpha]), |
| 128 | }), |
| 129 | write_mask: wgpu::ColorWrites::ALL, |
| 130 | })], |
| 131 | }), |
| 132 | primitive: Default::default(), |
| 133 | depth_stencil: None, |
| 134 | multisample: Default::default(), |
| 135 | multiview_mask: None, |
| 136 | cache: None, |
| 137 | }) |
| 138 | }); |
| 139 | let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { |
| 140 | label: Some("Draw vertices"), |
| 141 | size: VERTEX_BUFFER_BYTES, |
| 142 | usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, |
| 143 | mapped_at_creation: false, |
| 144 | }); |
| 145 | let sampler = |filter| { |
| 146 | device.create_sampler(&wgpu::SamplerDescriptor { |
| 147 | mag_filter: filter, |
| 148 | min_filter: filter, |
| 149 | ..Default::default() |
| 150 | }) |
| 151 | }; |
| 152 | let nearest = sampler(wgpu::FilterMode::Nearest); |
| 153 | let atlas = atlas(&device, &layout, &nearest, ATLAS_SIZE); |
| 154 | Self { |
| 155 | linear: sampler(wgpu::FilterMode::Linear), |
| 156 | nearest, |
| 157 | atlas, |
| 158 | device, |
| 159 | queue, |
| 160 | layout, |
| 161 | pipelines, |
| 162 | vertex_buffer, |
| 163 | format, |
| 164 | groups: Vec::new(), |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /// Blends in linear light, its targets being sRGB. |
| 169 | pub(super) fn blends_linear(&self) -> bool { |
| 170 | true |
| 171 | } |
| 172 | |
| 173 | pub(super) fn flipped(&self) -> bool { |
| 174 | false |
| 175 | } |
| 176 | |
| 177 | pub(super) fn max_texture_dimension(&self) -> u32 { |
| 178 | self.device.limits().max_texture_dimension_2d |
| 179 | } |
| 180 | |
| 181 | pub(super) fn atlas_side(&self) -> u32 { |
| 182 | self.atlas.0.width() |
| 183 | } |
| 184 | |
| 185 | pub(super) fn new_atlas(&mut self, side: u32) { |
| 186 | self.atlas = atlas(&self.device, &self.layout, &self.nearest, side); |
| 187 | } |
| 188 | |
| 189 | pub(super) fn write_atlas(&self, origin: [u32; 2], size: [u32; 2], rgba: &[u8]) { |
| 190 | self.queue.write_texture( |
| 191 | wgpu::TexelCopyTextureInfo { |
| 192 | origin: wgpu::Origin3d { |
| 193 | x: origin[0], |
| 194 | y: origin[1], |
| 195 | z: 0, |
| 196 | }, |
| 197 | ..self.atlas.0.as_image_copy() |
| 198 | }, |
| 199 | rgba, |
| 200 | wgpu::TexelCopyBufferLayout { |
| 201 | offset: 0, |
| 202 | bytes_per_row: Some(size[0] * 4), |
| 203 | rows_per_image: Some(size[1]), |
| 204 | }, |
| 205 | wgpu::Extent3d { |
| 206 | width: size[0], |
| 207 | height: size[1], |
| 208 | depth_or_array_layers: 1, |
| 209 | }, |
| 210 | ); |
| 211 | } |
| 212 | |
| 213 | pub(super) fn upload_image(&self, image: &RasterImage) -> Image { |
| 214 | let texture = texture( |
| 215 | &self.device, |
| 216 | image.size, |
| 217 | wgpu::TextureFormat::Rgba8UnormSrgb, |
| 218 | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, |
| 219 | ); |
| 220 | self.queue.write_texture( |
| 221 | texture.as_image_copy(), |
| 222 | image.pixels(), |
| 223 | wgpu::TexelCopyBufferLayout { |
| 224 | offset: 0, |
| 225 | bytes_per_row: Some(image.size[0] * 4), |
| 226 | rows_per_image: Some(image.size[1]), |
| 227 | }, |
| 228 | texture.size(), |
| 229 | ); |
| 230 | Image(binding(&self.device, &self.layout, &texture, &self.linear)) |
| 231 | } |
| 232 | |
| 233 | /// Clears `target`, `size` device pixels, to linear `clear` and draws the prepared |
| 234 | /// batches, each group's offscreen first. |
| 235 | pub(super) fn submit( |
| 236 | &mut self, |
| 237 | frame: &Frame<'_>, |
| 238 | target: &wgpu::TextureView, |
| 239 | size: [u32; 2], |
| 240 | clear: [f32; 4], |
| 241 | ) { |
| 242 | if self |
| 243 | .groups |
| 244 | .first() |
| 245 | .is_some_and(|(picture, _)| [picture.width(), picture.height()] != size) |
| 246 | { |
| 247 | self.groups.clear(); |
| 248 | } |
| 249 | while self.groups.len() < frame.groups.len() { |
| 250 | let picture = texture( |
| 251 | &self.device, |
| 252 | size, |
| 253 | self.format, |
| 254 | wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, |
| 255 | ); |
| 256 | let binding = binding(&self.device, &self.layout, &picture, &self.linear); |
| 257 | self.groups.push((picture, binding)); |
| 258 | } |
| 259 | let gpu = &*self; |
| 260 | gpu.queue |
| 261 | .write_buffer(&gpu.vertex_buffer, 0, bytemuck::cast_slice(frame.vertices)); |
| 262 | let mut encoder = gpu.device.create_command_encoder(&Default::default()); |
| 263 | let mut paint = |view: &wgpu::TextureView, clear: [f32; 4], batches: &[Batch]| { |
| 264 | let [r, g, b, a] = clear.map(f64::from); |
| 265 | let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { |
| 266 | label: Some("Draw"), |
| 267 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { |
| 268 | view, |
| 269 | depth_slice: None, |
| 270 | resolve_target: None, |
| 271 | ops: wgpu::Operations { |
| 272 | load: wgpu::LoadOp::Clear(wgpu::Color { r, g, b, a }), |
| 273 | store: wgpu::StoreOp::Store, |
| 274 | }, |
| 275 | })], |
| 276 | ..Default::default() |
| 277 | }); |
| 278 | pass.set_vertex_buffer(0, gpu.vertex_buffer.slice(..)); |
| 279 | for batch in batches { |
| 280 | let [x, y, width, height] = batch.scissor; |
| 281 | pass.set_scissor_rect(x, y, width, height); |
| 282 | pass.set_pipeline(&gpu.pipelines[batch.blend.state()]); |
| 283 | let binding = match batch.blend { |
| 284 | Blend::Image(id) => &frame.image::<Image>(id).0, |
| 285 | Blend::Group(index) => &gpu.groups[index].1, |
| 286 | Blend::Over | Blend::Erase | Blend::Multiply => &gpu.atlas.1, |
| 287 | }; |
| 288 | pass.set_bind_group(0, binding, &[]); |
| 289 | pass.draw(batch.vertices.clone(), 0..1); |
| 290 | } |
| 291 | }; |
| 292 | for (group, (picture, _)) in frame.groups.iter().zip(&gpu.groups) { |
| 293 | paint( |
| 294 | &picture.create_view(&Default::default()), |
| 295 | [0.0; 4], |
| 296 | &group.batches, |
| 297 | ); |
| 298 | } |
| 299 | paint(target, clear, frame.batches); |
| 300 | gpu.queue.submit([encoder.finish()]); |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | fn component([source, destination]: [Factor; 2]) -> wgpu::BlendComponent { |
| 305 | let factor = |factor| match factor { |
| 306 | Factor::Zero => wgpu::BlendFactor::Zero, |
| 307 | Factor::One => wgpu::BlendFactor::One, |
| 308 | Factor::SourceAlpha => wgpu::BlendFactor::SrcAlpha, |
| 309 | Factor::OneMinusSourceAlpha => wgpu::BlendFactor::OneMinusSrcAlpha, |
| 310 | Factor::Destination => wgpu::BlendFactor::Dst, |
| 311 | }; |
| 312 | wgpu::BlendComponent { |
| 313 | src_factor: factor(source), |
| 314 | dst_factor: factor(destination), |
| 315 | operation: wgpu::BlendOperation::Add, |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | fn texture( |
| 320 | device: &wgpu::Device, |
| 321 | [width, height]: [u32; 2], |
| 322 | format: wgpu::TextureFormat, |
| 323 | usage: wgpu::TextureUsages, |
| 324 | ) -> wgpu::Texture { |
| 325 | device.create_texture(&wgpu::TextureDescriptor { |
| 326 | label: Some("Draw"), |
| 327 | size: wgpu::Extent3d { |
| 328 | width, |
| 329 | height, |
| 330 | depth_or_array_layers: 1, |
| 331 | }, |
| 332 | mip_level_count: 1, |
| 333 | sample_count: 1, |
| 334 | dimension: wgpu::TextureDimension::D2, |
| 335 | format, |
| 336 | usage, |
| 337 | view_formats: &[], |
| 338 | }) |
| 339 | } |
| 340 | |
| 341 | /// How the pipelines sample `texture` through `sampler`. |
| 342 | fn binding( |
| 343 | device: &wgpu::Device, |
| 344 | layout: &wgpu::BindGroupLayout, |
| 345 | texture: &wgpu::Texture, |
| 346 | sampler: &wgpu::Sampler, |
| 347 | ) -> wgpu::BindGroup { |
| 348 | device.create_bind_group(&wgpu::BindGroupDescriptor { |
| 349 | label: Some("Draw"), |
| 350 | layout, |
| 351 | entries: &[ |
| 352 | wgpu::BindGroupEntry { |
| 353 | binding: 0, |
| 354 | resource: wgpu::BindingResource::TextureView( |
| 355 | &texture.create_view(&Default::default()), |
| 356 | ), |
| 357 | }, |
| 358 | wgpu::BindGroupEntry { |
| 359 | binding: 1, |
| 360 | resource: wgpu::BindingResource::Sampler(sampler), |
| 361 | }, |
| 362 | ], |
| 363 | }) |
| 364 | } |
| 365 | |
| 366 | /// An empty atlas `side` texels square, and how the pipelines sample it. |
| 367 | fn atlas( |
| 368 | device: &wgpu::Device, |
| 369 | layout: &wgpu::BindGroupLayout, |
| 370 | nearest: &wgpu::Sampler, |
| 371 | side: u32, |
| 372 | ) -> (wgpu::Texture, wgpu::BindGroup) { |
| 373 | let atlas = texture( |
| 374 | device, |
| 375 | [side; 2], |
| 376 | wgpu::TextureFormat::Rgba8UnormSrgb, |
| 377 | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, |
| 378 | ); |
| 379 | let binding = binding(device, layout, &atlas, nearest); |
| 380 | (atlas, binding) |
| 381 | } |