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