1use crate::{
2 app::App,
3 ecs::plugin::Plugin,
4 rendering::{
5 backend::{Backend, ColorTarget, FrameOperations, Pass},
6 errors::AcquireError,
7 sync::InitSender,
8 window::{GPUSurfaceHandle, WindowConfig},
9 },
10 threading::SpawnableFuture,
11 wgpu::window::WinitWindow,
12};
13
14pub struct WGPUBackend {
20 pub device: wgpu::Device,
21 pub queue: wgpu::Queue,
22 pub surface: wgpu::Surface<'static>,
23 pub config: wgpu::SurfaceConfiguration,
24}
25
26impl WGPUBackend {
27 async fn init_async(
28 handle: impl GPUSurfaceHandle,
29 width: u32,
30 height: u32,
31 sender: InitSender<Self>,
32 ) {
33 let backends = if cfg!(target_arch = "wasm32") {
34 wgpu::Backends::BROWSER_WEBGPU
35 } else {
36 wgpu::Backends::PRIMARY
37 };
38
39 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
40 display: None,
41 backends,
42 flags: wgpu::InstanceFlags::default(),
43 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
44 backend_options: wgpu::BackendOptions::default(),
45 });
46
47 let surface = instance.create_surface(handle).unwrap();
48
49 let adapter = instance
50 .request_adapter(&wgpu::RequestAdapterOptions {
51 power_preference: wgpu::PowerPreference::HighPerformance,
52 force_fallback_adapter: false,
53 compatible_surface: Some(&surface),
54 })
55 .await
56 .unwrap();
57
58 let (required_features, required_limits) = if cfg!(target_arch = "wasm32") {
59 (wgpu::Features::empty(), wgpu::Limits::defaults())
60 } else {
61 (
62 wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
63 wgpu::Limits::default(),
64 )
65 };
66
67 let (device, queue) = adapter
68 .request_device(&wgpu::DeviceDescriptor {
69 label: None,
70 required_features,
71 required_limits,
72 ..Default::default()
73 })
74 .await
75 .unwrap();
76
77 let caps = surface.get_capabilities(&adapter);
78 let format = caps
79 .formats
80 .iter()
81 .copied()
82 .find(|f| f.is_srgb())
83 .unwrap_or(caps.formats[0]);
84
85 let present_mode = caps
89 .present_modes
90 .iter()
91 .copied()
92 .find(|m| *m == wgpu::PresentMode::Fifo)
93 .unwrap_or(caps.present_modes[0]);
94
95 let config = wgpu::SurfaceConfiguration {
96 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
97 format,
98 present_mode,
99 alpha_mode: caps.alpha_modes[0],
100 width,
101 height,
102 desired_maximum_frame_latency: 2,
103 view_formats: vec![],
104 };
105 surface.configure(&device, &config);
106
107 sender.send(WGPUBackend {
108 device,
109 queue,
110 surface,
111 config,
112 });
113 }
114}
115
116pub struct WGPUFrame {
117 encoder: wgpu::CommandEncoder,
118 view: wgpu::TextureView,
119 surface_texture: wgpu::SurfaceTexture,
120}
121
122impl FrameOperations for WGPUFrame {
123 type Context<'a> = wgpu::RenderPass<'a>;
124 type Attachment = wgpu::TextureView;
125 type DepthAttachment = wgpu::TextureView;
126
127 fn begin(&mut self, pass: Pass<'_, Self>) -> Self::Context<'_> {
128 let color_attachments: Vec<_> = pass
129 .colors
130 .iter()
131 .map(|target| {
132 let (view, clear) = match target {
133 ColorTarget::Default { clear } => (&self.view, clear),
134 ColorTarget::Custom { attachment, clear } => (*attachment, clear),
135 };
136 Some(wgpu::RenderPassColorAttachment {
137 view,
138 depth_slice: None,
139 resolve_target: None,
140 ops: wgpu::Operations {
141 load: clear
142 .map(|[r, g, b, a]| {
143 wgpu::LoadOp::Clear(wgpu::Color {
144 r: r as f64,
145 g: g as f64,
146 b: b as f64,
147 a: a as f64,
148 })
149 })
150 .unwrap_or(wgpu::LoadOp::Load),
151 store: wgpu::StoreOp::Store,
152 },
153 })
154 })
155 .collect();
156
157 let depth_stencil_attachment =
158 pass.depth
159 .as_ref()
160 .map(|d| wgpu::RenderPassDepthStencilAttachment {
161 view: d.attachment,
162 depth_ops: Some(wgpu::Operations {
163 load: d
164 .clear
165 .map(wgpu::LoadOp::Clear)
166 .unwrap_or(wgpu::LoadOp::Load),
167 store: wgpu::StoreOp::Store,
168 }),
169 stencil_ops: None,
170 });
171
172 self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
173 label: None,
174 color_attachments: &color_attachments,
175 depth_stencil_attachment,
176 timestamp_writes: None,
177 occlusion_query_set: None,
178 multiview_mask: None,
179 })
180 }
181}
182
183impl WGPUFrame {
184 pub fn compute_pass(&mut self, label: Option<&str>) -> wgpu::ComputePass<'_> {
186 self.encoder
187 .begin_compute_pass(&wgpu::ComputePassDescriptor {
188 label,
189 timestamp_writes: None,
190 })
191 }
192}
193
194impl Backend for WGPUBackend {
195 type Frame = WGPUFrame;
196
197 fn init(handle: impl GPUSurfaceHandle, width: u32, height: u32, sender: InitSender<Self>) {
208 #[cfg(not(target_arch = "wasm32"))]
209 {
210 pollster::block_on(Self::init_async(handle, width, height, sender));
211 }
212
213 #[cfg(target_arch = "wasm32")]
214 {
215 wasm_bindgen_futures::spawn_local(Self::init_async(handle, width, height, sender));
216 }
217 }
218
219 fn resize(&mut self, width: u32, height: u32) {
220 if width == 0 || height == 0 {
221 return; }
223 self.config.width = width;
224 self.config.height = height;
225 self.surface.configure(&self.device, &self.config);
226 }
227
228 fn acquire(&mut self) -> Result<Self::Frame, AcquireError> {
229 let surface_texture = match self.surface.get_current_texture() {
230 wgpu::CurrentSurfaceTexture::Success(texture) => texture,
231 wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
232 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Outdated => {
233 return Err(AcquireError::Transient);
234 }
235 other => {
236 return Err(AcquireError::Fatal(format!(
237 "unexpected surface state: {other:?}"
238 )));
239 }
240 };
241
242 let view = surface_texture
243 .texture
244 .create_view(&wgpu::TextureViewDescriptor::default());
245 let encoder = self
246 .device
247 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
248
249 Ok(WGPUFrame {
250 encoder,
251 view,
252 surface_texture,
253 })
254 }
255
256 fn present(&mut self, frame: Self::Frame) {
257 self.queue.submit(std::iter::once(frame.encoder.finish()));
258 frame.surface_texture.present();
259 }
260}
261
262impl WGPUBackend {
263 pub fn readback_buffer(&self, src: &wgpu::Buffer) -> impl SpawnableFuture<Vec<u8>> {
279 use crate::wgpu::buffers::build_buffer_sized;
280
281 let size = src.size();
282 let staging = build_buffer_sized(
283 &self.device,
284 size,
285 wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
286 );
287
288 let mut encoder = self
289 .device
290 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
291 encoder.copy_buffer_to_buffer(src, 0, &staging, 0, size);
292 let idx = self.queue.submit(std::iter::once(encoder.finish()));
293
294 #[cfg(not(target_arch = "wasm32"))]
295 let device = self.device.clone();
296
297 async move {
298 #[cfg(not(target_arch = "wasm32"))]
299 {
300 let (tx, rx) = std::sync::mpsc::channel();
301 staging.slice(..).map_async(wgpu::MapMode::Read, move |r| {
302 let _ = tx.send(r);
303 });
304 let _ = device.poll(wgpu::PollType::Wait {
311 submission_index: Some(idx),
312 timeout: None,
313 });
314 rx.recv().unwrap().unwrap();
315 let data = staging.slice(..).get_mapped_range().to_vec();
316 staging.unmap();
317 data
318 }
319
320 #[cfg(target_arch = "wasm32")]
321 {
322 let _ = idx;
323 let mapped: std::sync::Arc<
324 std::sync::Mutex<Option<Result<(), wgpu::BufferAsyncError>>>,
325 > = std::sync::Arc::new(std::sync::Mutex::new(None));
326 let waker: std::sync::Arc<std::sync::Mutex<Option<std::task::Waker>>> =
327 std::sync::Arc::new(std::sync::Mutex::new(None));
328
329 let mapped_cb = mapped.clone();
330 let waker_cb = waker.clone();
331 staging.slice(..).map_async(wgpu::MapMode::Read, move |r| {
332 *mapped_cb.lock().unwrap() = Some(r);
333 if let Some(w) = waker_cb.lock().unwrap().take() {
334 w.wake();
335 }
336 });
337
338 std::future::poll_fn(|cx| {
339 let mut guard = mapped.lock().unwrap();
340 if let Some(r) = guard.take() {
341 std::task::Poll::Ready(r)
342 } else {
343 *waker.lock().unwrap() = Some(cx.waker().clone());
344 std::task::Poll::Pending
345 }
346 })
347 .await
348 .unwrap();
349
350 let data = staging.slice(..).get_mapped_range().to_vec();
351 staging.unmap();
352 data
353 }
354 }
355 }
356
357 pub fn readback_buffer_as<T: bytemuck::Pod + Send + 'static>(
360 &self,
361 src: &wgpu::Buffer,
362 ) -> impl SpawnableFuture<Vec<T>> {
363 let bytes = self.readback_buffer(src);
364 async move {
365 let bytes = bytes.await;
366 bytemuck::cast_slice(&bytes).to_vec()
367 }
368 }
369}
370
371pub struct WGPUPlugin {
372 config: WindowConfig,
373}
374
375impl WGPUPlugin {
376 pub fn new(config: WindowConfig) -> Self {
377 Self { config }
378 }
379}
380
381impl Plugin for WGPUPlugin {
382 fn build(&self, app: &mut App) {
383 app.add_plugin(crate::prelude::WindowPlugin::<WinitWindow>::new(
384 WindowConfig {
385 title: self.config.title.clone(),
386 width: self.config.width,
387 height: self.config.height,
388 },
389 ))
390 .add_plugin(crate::prelude::GraphicsPlugin::<WGPUBackend, WinitWindow>::new())
391 .add_plugin(crate::prelude::RenderPlugin::<WGPUBackend>::new())
392 .add_plugin(crate::wgpu::textures::TexturePlugin)
393 .add_plugin(crate::wgpu::texture_array::TextureArrayPlugin)
394 .add_plugin(crate::wgpu::cubemap::CubemapPlugin)
395 .add_plugin(crate::wgpu::mesh::MeshPlugin::new())
396 .add_plugin(crate::wgpu::material::MaterialPlugin::new())
397 .add_plugin(crate::wgpu::instance::MaterialInstancePlugin::new())
398 .add_plugin(crate::wgpu::compute::ComputePlugin::new())
399 .add_plugin(crate::wgpu::instance::ComputeInstancePlugin::new())
400 .add_plugin(crate::prelude::LazyResourcePlugin::<
401 WGPUBackend,
402 crate::wgpu::samplers::GlobalSamplers,
403 >::new());
404 }
405}