1use std::{fmt::Debug, marker::PhantomData, ops::Deref};
2
3use tracing::{info, warn};
4use wgpu::util::DeviceExt;
5
6#[derive(bevy_ecs::prelude::Resource, Clone)]
8pub struct WgpuContext {
9 pub instance: wgpu::Instance,
11 pub adapter: wgpu::Adapter,
13 pub device: wgpu::Device,
15 pub queue: wgpu::Queue,
17}
18
19impl WgpuContext {
20 pub async fn new() -> Self {
22 let instance = wgpu::Instance::default();
23 let adapter = instance
24 .request_adapter(&wgpu::RequestAdapterOptions {
25 power_preference: wgpu::PowerPreference::HighPerformance,
26 ..Default::default()
27 })
28 .await
29 .unwrap();
30 info!("wgpu adapter info: {:?}", adapter.get_info());
31 let required_limits = adapter.limits();
32 let required_features =
36 adapter.features() & wgpu_profiler::GpuProfiler::ALL_WGPU_TIMER_FEATURES;
37 let (device, queue) = adapter
38 .request_device(&wgpu::DeviceDescriptor {
39 required_features,
40 required_limits,
41 ..Default::default()
42 })
43 .await
44 .unwrap();
45
46 Self {
47 instance,
48 adapter,
49 device,
50 queue,
51 }
52 }
53}
54
55#[allow(unused)]
56pub(crate) struct WgpuBuffer<T: bytemuck::Pod + bytemuck::Zeroable + Debug> {
57 label: Option<&'static str>,
58 buffer: wgpu::Buffer,
59 usage: wgpu::BufferUsages,
60 inner: T,
61}
62
63impl<T: bytemuck::Pod + bytemuck::Zeroable + Debug> AsRef<wgpu::Buffer> for WgpuBuffer<T> {
64 fn as_ref(&self) -> &wgpu::Buffer {
65 &self.buffer
66 }
67}
68
69#[allow(unused)]
70impl<T: bytemuck::Pod + bytemuck::Zeroable + Debug> WgpuBuffer<T> {
71 pub(crate) fn new_init(
72 ctx: &WgpuContext,
73 label: Option<&'static str>,
74 usage: wgpu::BufferUsages,
75 data: T,
76 ) -> Self {
77 assert!(
78 usage.contains(wgpu::BufferUsages::COPY_DST),
79 "Buffer {label:?} does not contains COPY_DST"
80 );
81 Self {
83 label,
84 buffer: ctx
85 .device
86 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
87 label,
88 contents: bytemuck::bytes_of(&data),
89 usage,
90 }),
91 usage,
92 inner: data,
93 }
94 }
95
96 pub(crate) fn get(&self) -> &T {
97 &self.inner
98 }
99
100 pub(crate) fn set(&mut self, ctx: &WgpuContext, data: T) {
101 let start = std::time::Instant::now();
102 {
103 let mut view = ctx
104 .queue
105 .write_buffer_with(
106 &self.buffer,
107 0,
108 wgpu::BufferSize::new(std::mem::size_of_val(&data) as u64).unwrap(),
109 )
110 .unwrap();
111 view.copy_from_slice(bytemuck::bytes_of(&data));
112 }
113 if crate::upload_probe::mode().enabled() {
114 crate::upload_probe::record(
115 self.label,
116 std::mem::size_of_val(&data) as u64,
117 std::mem::size_of_val(&data) as u64,
118 start.elapsed().as_nanos() as u64,
119 );
120 }
121 self.inner = data;
123 }
124
125 #[allow(unused)]
126 pub(crate) fn read_buffer(&self, ctx: &WgpuContext) -> Vec<u8> {
127 let size = std::mem::size_of::<T>();
128 let staging_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
129 label: Some("Debug Staging Buffer"),
130 size: size as u64,
131 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
132 mapped_at_creation: false,
133 });
134
135 let mut encoder = ctx
136 .device
137 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
138 label: Some("Debug Read Encoder"),
139 });
140
141 encoder.copy_buffer_to_buffer(&self.buffer, 0, &staging_buffer, 0, size as u64);
142 ctx.queue.submit(Some(encoder.finish()));
143
144 let buffer_slice = staging_buffer.slice(..);
145 let (tx, rx) = async_channel::bounded(1);
146 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
147 pollster::block_on(tx.send(result)).unwrap()
148 });
149 ctx.device
150 .poll(wgpu::PollType::wait_indefinitely())
151 .unwrap();
152 pollster::block_on(rx.recv()).unwrap().unwrap();
153
154 buffer_slice.get_mapped_range().unwrap().to_vec()
155 }
156}
157
158pub(crate) struct WgpuVecBuffer<T: Default + bytemuck::Pod + bytemuck::Zeroable + Debug> {
159 label: Option<&'static str>,
160 pub(crate) buffer: wgpu::Buffer,
161 usage: wgpu::BufferUsages,
162 len: usize,
164 _phantom: PhantomData<T>,
165 }
167
168impl<T: Default + bytemuck::Pod + bytemuck::Zeroable + Debug> WgpuVecBuffer<T> {
169 pub(crate) fn new(
170 ctx: &WgpuContext,
171 label: Option<&'static str>,
172 usage: wgpu::BufferUsages,
173 len: usize,
174 ) -> Self {
175 assert!(
176 usage.contains(wgpu::BufferUsages::COPY_DST),
177 "Buffer {label:?} does not contains COPY_DST"
178 );
179 let size = (std::mem::size_of::<T>() * len) as u64;
180 Self {
181 label,
182 buffer: ctx.device.create_buffer(&wgpu::BufferDescriptor {
183 label,
184 size,
185 usage,
186 mapped_at_creation: false,
187 }),
188 usage,
189 len: 0,
190 _phantom: PhantomData,
191 }
193 }
194
195 #[allow(unused)]
196 pub(crate) fn new_init(
197 ctx: &WgpuContext,
198 label: Option<&'static str>,
199 usage: wgpu::BufferUsages,
200 data: &[T],
201 ) -> Self {
202 let mut buffer = Self::new(ctx, label, usage, data.len());
203 buffer.set(ctx, data);
204 buffer
205 }
206
207 #[allow(unused)]
208 pub(crate) fn len(&self) -> usize {
209 self.len
210 }
211 #[allow(unused)]
216 pub(crate) fn resize(&mut self, ctx: &WgpuContext, len: usize) -> bool {
217 let size = (std::mem::size_of::<T>() * len) as u64;
218 let realloc = self.buffer.size() != size;
219 if realloc {
220 self.len = len;
221 self.buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
223 label: self.label,
224 size,
225 usage: self.usage,
226 mapped_at_creation: false,
227 });
228 }
229 realloc
230 }
231
232 pub(crate) fn set(&mut self, ctx: &WgpuContext, data: &[T]) -> bool {
233 self.len = data.len();
237 let realloc = self.buffer.size() != std::mem::size_of_val(data) as u64;
238 let count = crate::upload_probe::mode().enabled();
239 let bytes = bytemuck::cast_slice(data);
240
241 if realloc {
242 let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
245 label: self.label,
246 size: (std::mem::size_of_val(data)) as u64,
247 usage: self.usage,
248 mapped_at_creation: false,
249 });
250 let start = std::time::Instant::now();
251 ctx.queue.write_buffer(&buffer, 0, bytes);
252 if count {
253 crate::upload_probe::record(
254 self.label,
255 bytes.len() as u64,
256 bytes.len() as u64,
257 start.elapsed().as_nanos() as u64,
258 );
259 }
260 self.buffer = buffer;
262 } else {
263 let start = std::time::Instant::now();
265 {
266 let mut view = ctx
267 .queue
268 .write_buffer_with(
269 &self.buffer,
270 0,
271 wgpu::BufferSize::new((std::mem::size_of_val(data)) as u64).unwrap(),
272 )
273 .unwrap();
274 view.copy_from_slice(bytes);
275 }
276 if count {
277 crate::upload_probe::record(
278 self.label,
279 bytes.len() as u64,
280 bytes.len() as u64,
281 start.elapsed().as_nanos() as u64,
282 );
283 }
284 }
286 realloc
288 }
289
290 #[allow(unused)]
291 pub(crate) fn read_buffer(&self, ctx: &WgpuContext) -> Option<Vec<u8>> {
292 let size = std::mem::size_of::<T>() * self.len;
293 let staging_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
294 label: Some("Debug Staging Buffer"),
295 size: size as u64,
296 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
297 mapped_at_creation: false,
298 });
299
300 let mut encoder = ctx
301 .device
302 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
303 label: Some("Debug Read Encoder"),
304 });
305
306 encoder.copy_buffer_to_buffer(&self.buffer, 0, &staging_buffer, 0, size as u64);
307 ctx.queue.submit(Some(encoder.finish()));
308
309 let buffer_slice = staging_buffer.slice(..);
310 let (tx, rx) = async_channel::bounded(1);
311 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
312 tx.try_send(result).unwrap()
313 });
314 ctx.device
315 .poll(wgpu::PollType::wait_indefinitely())
316 .unwrap();
317 pollster::block_on(rx.recv()).unwrap().unwrap();
318
319 let x = buffer_slice.get_mapped_range().unwrap().to_vec();
320 Some(x)
321 }
322}
323
324pub struct WgpuTexture {
325 inner: wgpu::Texture,
326}
327
328impl WgpuTexture {
329 pub fn new(ctx: &WgpuContext, desc: &wgpu::TextureDescriptor) -> Self {
330 Self {
331 inner: ctx.device.create_texture(desc),
332 }
333 }
334}
335
336impl Deref for WgpuTexture {
337 type Target = wgpu::Texture;
338 fn deref(&self) -> &Self::Target {
339 &self.inner
340 }
341}
342
343pub struct ReadbackWgpuTexture {
346 inner: WgpuTexture,
347 aligned_bytes_per_row: usize,
348 staging_buffer: wgpu::Buffer,
349 bytes: Vec<u8>,
350 pending_rx: Option<async_channel::Receiver<Result<(), wgpu::BufferAsyncError>>>,
353}
354
355impl Deref for ReadbackWgpuTexture {
356 type Target = WgpuTexture;
357 fn deref(&self) -> &Self::Target {
358 &self.inner
359 }
360}
361
362const ALIGNMENT: usize = 256;
363impl ReadbackWgpuTexture {
364 pub fn new(ctx: &WgpuContext, desc: &wgpu::TextureDescriptor) -> Self {
365 if !desc.usage.contains(wgpu::TextureUsages::COPY_SRC) {
366 warn!(
367 "ReadbackWgpuTexture should have COPY_SRC usage, but got {:?}, will auto add this usage",
368 desc.usage
369 );
370 }
371 let texture = WgpuTexture::new(
372 ctx,
373 &wgpu::TextureDescriptor {
374 usage: desc.usage | wgpu::TextureUsages::COPY_SRC,
375 ..*desc
376 },
377 );
378 let block_size = desc.format.block_copy_size(None).unwrap();
379 let bytes_per_row =
380 (texture.size().width * block_size).div_ceil(ALIGNMENT as u32) as usize * ALIGNMENT;
381
382 let staging_buffer_label = desc.label.map(|s| format!("{s} Staging Buffer"));
383 let staging_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
384 label: staging_buffer_label.as_deref(),
385 size: (bytes_per_row * texture.size().height as usize) as u64,
386 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
387 mapped_at_creation: false,
388 });
389 let len = texture.size().width * texture.size().height * block_size;
390 let bytes = vec![0u8; len as usize];
391
392 Self {
393 inner: texture,
394 aligned_bytes_per_row: bytes_per_row,
395 staging_buffer,
396 bytes,
397 pending_rx: None,
398 }
399 }
400 pub fn texture_data(&self) -> &[u8] {
401 &self.bytes
402 }
403
404 pub fn start_readback(&mut self, ctx: &WgpuContext) {
409 let size = self.size();
410
411 let mut encoder = ctx
412 .device
413 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
414 label: Some("Readback Copy Encoder"),
415 });
416 encoder.copy_texture_to_buffer(
417 wgpu::TexelCopyTextureInfo {
418 aspect: wgpu::TextureAspect::All,
419 texture: self,
420 mip_level: 0,
421 origin: wgpu::Origin3d::ZERO,
422 },
423 wgpu::TexelCopyBufferInfo {
424 buffer: &self.staging_buffer,
425 layout: wgpu::TexelCopyBufferLayout {
426 offset: 0,
427 bytes_per_row: Some(self.aligned_bytes_per_row as u32),
428 rows_per_image: Some(size.height),
429 },
430 },
431 size,
432 );
433 ctx.queue.submit(Some(encoder.finish()));
434
435 let buffer_slice = self.staging_buffer.slice(..);
436 let (tx, rx) = async_channel::bounded(1);
437 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
438 let _ = tx.try_send(result);
439 });
440 self.pending_rx = Some(rx);
441 }
442
443 pub fn finish_readback(&mut self, ctx: &WgpuContext) {
448 let Some(rx) = self.pending_rx.take() else {
449 return;
450 };
451
452 ctx.device
453 .poll(wgpu::PollType::wait_indefinitely())
454 .unwrap();
455 pollster::block_on(rx.recv()).unwrap().unwrap();
456
457 self.copy_staging_to_bytes();
458 }
459
460 pub fn try_finish_readback(&mut self, ctx: &WgpuContext) -> bool {
463 let Some(rx) = self.pending_rx.as_ref() else {
464 return true;
465 };
466
467 let _ = ctx.device.poll(wgpu::PollType::Poll);
469
470 match rx.try_recv() {
472 Ok(result) => {
473 result.unwrap();
474 self.pending_rx = None;
475 self.copy_staging_to_bytes();
476 true
477 }
478 Err(async_channel::TryRecvError::Empty) => false,
479 Err(async_channel::TryRecvError::Closed) => {
480 self.pending_rx = None;
481 true
482 }
483 }
484 }
485
486 fn copy_staging_to_bytes(&mut self) {
487 let size = self.size();
488 let buffer_slice = self.staging_buffer.slice(..);
489 let view = buffer_slice.get_mapped_range().unwrap();
490 let block_size = self.inner.format().block_copy_size(None).unwrap();
491 let bytes_in_row = (size.width * block_size) as usize;
492
493 for y in 0..size.height as usize {
494 let src_row_start = y * self.aligned_bytes_per_row;
495 let dst_row_start = y * bytes_in_row;
496
497 self.bytes[dst_row_start..dst_row_start + bytes_in_row]
498 .copy_from_slice(&view[src_row_start..src_row_start + bytes_in_row]);
499 }
500 drop(view);
501 self.staging_buffer.unmap();
502 }
503
504 pub fn update_texture_data(&mut self, ctx: &WgpuContext) -> &[u8] {
506 self.start_readback(ctx);
507 self.finish_readback(ctx);
508 &self.bytes
509 }
510}