1//! Submission through wgpu: Metal, Vulkan or GL, whichever the platform offers.
2use super::*;
3
4/// What a frame is drawn into.
5#[cfg(not(any(windows, target_os = "macos", target_arch = "wasm32")))]
6pub type Target = wgpu::TextureView;
7
8/// A picture's texture, as its batches bind it.
9pub(super) struct Image(wgpu::BindGroup);
10
11impl AsRef<Image> for Image {
12 fn as_ref(&self) -> &Image {
13 self
14 }
15}
16
17pub(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
32impl 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))]
49impl 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
59impl 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
304fn 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
319fn 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`.
342fn 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.
367fn 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}