1use std::{
8 ops::Deref,
9 sync::{
10 Arc,
11 atomic::{AtomicBool, AtomicU64, Ordering},
12 },
13};
14
15use parking_lot::Mutex;
16use rustc_hash::FxHashMap;
17use wgpu::util::DeviceExt as _;
18
19pub use self::{
20 frame::FrameBuffer,
21 scratch::{ScratchFit, ScratchTexture, ScratchTextureDescriptor},
22};
23
24mod frame;
25mod scratch;
26
27#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
29#[cfg_attr(feature = "headless", derive(serde::Serialize))]
30pub struct GraphicsWork {
31 pub buffer_allocations: usize,
33 pub buffer_uploads: usize,
35 pub buffer_uploaded_bytes: u64,
37 pub texture_allocations: usize,
39 pub texture_uploads: usize,
41 pub texture_uploaded_bytes: u64,
43}
44
45#[derive(Clone, Debug, Default)]
47#[cfg_attr(feature = "headless", derive(serde::Serialize))]
48pub struct GraphicsWorkDiagnostics {
49 #[cfg_attr(feature = "headless", serde(skip))]
50 owner: u128,
51 groups: FxHashMap<&'static str, GraphicsWork>,
52}
53
54impl GraphicsWorkDiagnostics {
55 pub fn since(&self, earlier: &Self) -> Self {
57 assert_eq!(
58 self.owner, earlier.owner,
59 "graphics snapshots belong to different renderers"
60 );
61 assert!(
62 earlier
63 .groups
64 .keys()
65 .all(|name| self.groups.contains_key(name)),
66 "graphics baseline is newer than snapshot"
67 );
68 let mut difference = self.clone();
69 for (name, value) in &mut difference.groups {
70 let before = earlier.group(name);
71 value.buffer_allocations = value
72 .buffer_allocations
73 .checked_sub(before.buffer_allocations)
74 .expect("graphics baseline is newer than snapshot");
75 value.buffer_uploads = value
76 .buffer_uploads
77 .checked_sub(before.buffer_uploads)
78 .expect("graphics baseline is newer than snapshot");
79 value.buffer_uploaded_bytes = value
80 .buffer_uploaded_bytes
81 .checked_sub(before.buffer_uploaded_bytes)
82 .expect("graphics baseline is newer than snapshot");
83 value.texture_allocations = value
84 .texture_allocations
85 .checked_sub(before.texture_allocations)
86 .expect("graphics baseline is newer than snapshot");
87 value.texture_uploads = value
88 .texture_uploads
89 .checked_sub(before.texture_uploads)
90 .expect("graphics baseline is newer than snapshot");
91 value.texture_uploaded_bytes = value
92 .texture_uploaded_bytes
93 .checked_sub(before.texture_uploaded_bytes)
94 .expect("graphics baseline is newer than snapshot");
95 }
96 difference
97 }
98 pub fn group(&self, group: &str) -> GraphicsWork {
100 self.groups.get(group).copied().unwrap_or_default()
101 }
102}
103
104#[derive(Clone, Debug)]
106pub struct GraphicsDevice {
107 device: wgpu::Device,
108 queue: wgpu::Queue,
109 work: Arc<Mutex<GraphicsWorkDiagnostics>>,
110 frame_uploads: Arc<FrameUploads>,
111 prelude: Arc<Mutex<Option<wgpu::CommandEncoder>>>,
112 scratch: Arc<Mutex<scratch::ScratchPool>>,
113 frame_resources: Arc<Mutex<frame::FrameResources>>,
114}
115
116#[derive(Debug, Default)]
117struct FrameUploads {
118 bytes: AtomicU64,
119 deferred: AtomicBool,
120}
121
122impl GraphicsDevice {
123 pub(crate) fn new(device: wgpu::Device, queue: wgpu::Queue) -> Self {
124 Self {
125 device,
126 queue,
127 work: Arc::new(Mutex::new(GraphicsWorkDiagnostics {
128 owner: uuid::Uuid::new_v4().as_u128(),
129 groups: FxHashMap::default(),
130 })),
131 frame_uploads: Arc::default(),
132 prelude: Arc::default(),
133 scratch: Arc::default(),
134 frame_resources: Arc::default(),
135 }
136 }
137
138 pub fn encode_before_frame(&self, record: impl FnOnce(&mut wgpu::CommandEncoder)) {
144 let mut prelude = self.prelude.lock();
145 record(prelude.get_or_insert_with(|| {
146 self.device
147 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
148 label: Some("Frame Prelude"),
149 })
150 }));
151 }
152
153 pub(crate) fn finish_prelude(&self) -> Option<wgpu::CommandBuffer> {
154 self.prelude.lock().take().map(wgpu::CommandEncoder::finish)
155 }
156
157 pub fn device(&self) -> &wgpu::Device {
159 &self.device
160 }
161
162 pub fn queue(&self) -> &wgpu::Queue {
164 &self.queue
165 }
166
167 pub fn upload_budget(&self) -> u64 {
172 FRAME_UPLOAD_BUDGET.saturating_sub(self.frame_uploads.bytes.load(Ordering::Relaxed))
173 }
174
175 pub fn defer_upload(&self) {
177 self.frame_uploads.deferred.store(true, Ordering::Relaxed);
178 }
179
180 pub(crate) fn begin_frame(&self) {
181 self.frame_uploads.bytes.store(0, Ordering::Relaxed);
182 self.scratch.lock().begin_frame();
183 self.frame_resources.lock().begin_frame();
184 }
185
186 pub(crate) fn take_deferred_upload(&self) -> bool {
187 self.frame_uploads.deferred.swap(false, Ordering::Relaxed)
188 }
189
190 pub fn diagnostics(&self) -> GraphicsWorkDiagnostics {
192 self.work.lock().clone()
193 }
194
195 pub fn create_buffer(
197 &self,
198 group: &'static str,
199 descriptor: &wgpu::BufferDescriptor<'_>,
200 ) -> TrackedBuffer {
201 let buffer = self.device.create_buffer(descriptor);
202 self.work
203 .lock()
204 .groups
205 .entry(group)
206 .or_default()
207 .buffer_allocations += 1;
208 TrackedBuffer {
209 buffer,
210 group,
211 graphics: self.clone(),
212 }
213 }
214
215 pub fn create_buffer_init(
217 &self,
218 group: &'static str,
219 descriptor: &wgpu::util::BufferInitDescriptor<'_>,
220 ) -> TrackedBuffer {
221 let buffer = self.device.create_buffer_init(descriptor);
222 self.work
223 .lock()
224 .groups
225 .entry(group)
226 .or_default()
227 .buffer_allocations += 1;
228 TrackedBuffer {
229 buffer,
230 group,
231 graphics: self.clone(),
232 }
233 }
234
235 pub fn write_buffer(&self, buffer: &TrackedBuffer, offset: u64, data: &[u8]) {
237 assert!(
238 Arc::ptr_eq(&self.work, &buffer.graphics.work),
239 "buffer belongs to another renderer"
240 );
241 self.queue.write_buffer(&buffer.buffer, offset, data);
242 let mut work = self.work.lock();
243 let group = work.groups.entry(buffer.group).or_default();
244 group.buffer_uploads += 1;
245 group.buffer_uploaded_bytes += data.len() as u64;
246 }
247
248 pub fn create_texture(
250 &self,
251 group: &'static str,
252 descriptor: &wgpu::TextureDescriptor<'_>,
253 ) -> wgpu::Texture {
254 let texture = self.device.create_texture(descriptor);
255 self.work
256 .lock()
257 .groups
258 .entry(group)
259 .or_default()
260 .texture_allocations += 1;
261 texture
262 }
263
264 pub fn write_texture(
266 &self,
267 group: &'static str,
268 destination: wgpu::TexelCopyTextureInfo<'_>,
269 data: &[u8],
270 layout: wgpu::TexelCopyBufferLayout,
271 size: wgpu::Extent3d,
272 ) {
273 match upload_band_rows(&destination, layout, size) {
274 Some((band_rows, block_height, bytes_per_row)) => {
275 let mut row = 0;
276 while row < size.height {
277 let height = band_rows.min(size.height - row);
278 let start = layout.offset + u64::from(row / block_height) * bytes_per_row;
279 self.queue.write_texture(
280 wgpu::TexelCopyTextureInfo {
281 origin: wgpu::Origin3d {
282 y: destination.origin.y + row,
283 ..destination.origin
284 },
285 ..destination
286 },
287 &data[usize::try_from(start).expect("upload offset")..],
288 wgpu::TexelCopyBufferLayout {
289 offset: 0,
290 bytes_per_row: layout.bytes_per_row,
291 rows_per_image: None,
292 },
293 wgpu::Extent3d { height, ..size },
294 );
295 row += height;
296 }
297 }
298 None => self.queue.write_texture(destination, data, layout, size),
299 }
300 self.frame_uploads
301 .bytes
302 .fetch_add(data.len() as u64, Ordering::Relaxed);
303 let mut work = self.work.lock();
304 let group = work.groups.entry(group).or_default();
305 group.texture_uploads += 1;
306 group.texture_uploaded_bytes += data.len() as u64;
307 }
308}
309
310const FRAME_UPLOAD_BUDGET: u64 = 4 << 20;
311
312const UPLOAD_BAND_BYTES: u64 = 1 << 20;
316
317fn upload_band_rows(
318 destination: &wgpu::TexelCopyTextureInfo<'_>,
319 layout: wgpu::TexelCopyBufferLayout,
320 size: wgpu::Extent3d,
321) -> Option<(u32, u32, u64)> {
322 let bytes_per_row = u64::from(layout.bytes_per_row?);
323 let (_, block_height) = destination.texture.format().block_dimensions();
324 let block_rows = size.height.div_ceil(block_height);
325 if size.depth_or_array_layers != 1 || bytes_per_row * u64::from(block_rows) <= UPLOAD_BAND_BYTES
326 {
327 return None;
328 }
329 let band_blocks = u32::try_from((UPLOAD_BAND_BYTES / bytes_per_row).max(1)).ok()?;
330 Some((band_blocks * block_height, block_height, bytes_per_row))
331}
332
333impl Deref for GraphicsDevice {
334 type Target = wgpu::Device;
335 fn deref(&self) -> &Self::Target {
336 &self.device
337 }
338}
339
340#[derive(Clone, Debug)]
342pub struct TrackedBuffer {
343 buffer: wgpu::Buffer,
344 group: &'static str,
345 graphics: GraphicsDevice,
346}
347
348impl Deref for TrackedBuffer {
349 type Target = wgpu::Buffer;
350 fn deref(&self) -> &Self::Target {
351 &self.buffer
352 }
353}