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 wgpu::window::WinitWindow,
11};
12
13pub struct WGPUBackend {
27 pub device: wgpu::Device,
28 pub queue: wgpu::Queue,
29 pub surface: wgpu::Surface<'static>,
30 pub config: wgpu::SurfaceConfiguration,
31}
32
33impl WGPUBackend {
34 async fn init_async(
35 handle: impl GPUSurfaceHandle,
36 width: u32,
37 height: u32,
38 sender: InitSender<Self>,
39 ) {
40 let backends = if cfg!(target_arch = "wasm32") {
41 wgpu::Backends::BROWSER_WEBGPU
42 } else {
43 wgpu::Backends::PRIMARY
44 };
45
46 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
47 display: None,
48 backends,
49 flags: wgpu::InstanceFlags::default(),
50 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
51 backend_options: wgpu::BackendOptions::default(),
52 });
53
54 let surface = instance.create_surface(handle).unwrap();
55
56 let adapter = instance
57 .request_adapter(&wgpu::RequestAdapterOptions {
58 power_preference: wgpu::PowerPreference::HighPerformance,
59 force_fallback_adapter: false,
60 compatible_surface: Some(&surface),
61 })
62 .await
63 .unwrap();
64
65 let (required_features, required_limits) = if cfg!(target_arch = "wasm32") {
66 (wgpu::Features::empty(), wgpu::Limits::defaults())
67 } else {
68 (
69 wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
70 wgpu::Limits::default(),
71 )
72 };
73
74 let (device, queue) = adapter
75 .request_device(&wgpu::DeviceDescriptor {
76 label: None,
77 required_features,
78 required_limits,
79 ..Default::default()
80 })
81 .await
82 .unwrap();
83
84 let caps = surface.get_capabilities(&adapter);
85 let format = caps
86 .formats
87 .iter()
88 .copied()
89 .find(|f| f.is_srgb())
90 .unwrap_or(caps.formats[0]);
91
92 let present_mode = caps
96 .present_modes
97 .iter()
98 .copied()
99 .find(|m| *m == wgpu::PresentMode::Fifo)
100 .unwrap_or(caps.present_modes[0]);
101
102 let config = wgpu::SurfaceConfiguration {
103 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
104 format,
105 present_mode,
106 alpha_mode: caps.alpha_modes[0],
107 width,
108 height,
109 desired_maximum_frame_latency: 2,
110 view_formats: vec![],
111 };
112 surface.configure(&device, &config);
113
114 sender.send(WGPUBackend {
115 device,
116 queue,
117 surface,
118 config,
119 });
120 }
121}
122
123pub struct WGPUFrame {
124 encoder: wgpu::CommandEncoder,
125 view: wgpu::TextureView,
126 surface_texture: wgpu::SurfaceTexture,
127}
128
129impl FrameOperations for WGPUFrame {
130 type Context<'a> = wgpu::RenderPass<'a>;
131 type Attachment = wgpu::TextureView;
132 type DepthAttachment = wgpu::TextureView;
133
134 fn begin(&mut self, pass: Pass<'_, Self>) -> Self::Context<'_> {
135 let color_attachments: Vec<_> = pass
136 .colors
137 .iter()
138 .map(|target| {
139 let (view, clear) = match target {
140 ColorTarget::Default { clear } => (&self.view, clear),
141 ColorTarget::Custom { attachment, clear } => (*attachment, clear),
142 };
143 Some(wgpu::RenderPassColorAttachment {
144 view,
145 depth_slice: None,
146 resolve_target: None,
147 ops: wgpu::Operations {
148 load: clear
149 .map(|[r, g, b, a]| {
150 wgpu::LoadOp::Clear(wgpu::Color {
151 r: r as f64,
152 g: g as f64,
153 b: b as f64,
154 a: a as f64,
155 })
156 })
157 .unwrap_or(wgpu::LoadOp::Load),
158 store: wgpu::StoreOp::Store,
159 },
160 })
161 })
162 .collect();
163
164 let depth_stencil_attachment =
165 pass.depth
166 .as_ref()
167 .map(|d| wgpu::RenderPassDepthStencilAttachment {
168 view: d.attachment,
169 depth_ops: Some(wgpu::Operations {
170 load: d
171 .clear
172 .map(wgpu::LoadOp::Clear)
173 .unwrap_or(wgpu::LoadOp::Load),
174 store: wgpu::StoreOp::Store,
175 }),
176 stencil_ops: None,
177 });
178
179 self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
180 label: None,
181 color_attachments: &color_attachments,
182 depth_stencil_attachment,
183 timestamp_writes: None,
184 occlusion_query_set: None,
185 multiview_mask: None,
186 })
187 }
188}
189
190impl WGPUFrame {
191 pub fn compute_pass(&mut self, label: Option<&str>) -> wgpu::ComputePass<'_> {
193 self.encoder
194 .begin_compute_pass(&wgpu::ComputePassDescriptor {
195 label,
196 timestamp_writes: None,
197 })
198 }
199}
200
201impl Backend for WGPUBackend {
202 type Frame = WGPUFrame;
203
204 fn init(handle: impl GPUSurfaceHandle, width: u32, height: u32, sender: InitSender<Self>) {
215 #[cfg(not(target_arch = "wasm32"))]
216 {
217 pollster::block_on(Self::init_async(handle, width, height, sender));
218 }
219
220 #[cfg(target_arch = "wasm32")]
221 {
222 wasm_bindgen_futures::spawn_local(Self::init_async(handle, width, height, sender));
223 }
224 }
225
226 fn resize(&mut self, width: u32, height: u32) {
227 if width == 0 || height == 0 {
228 return; }
230 self.config.width = width;
231 self.config.height = height;
232 self.surface.configure(&self.device, &self.config);
233 }
234
235 fn acquire(&mut self) -> Result<Self::Frame, AcquireError> {
236 let surface_texture = match self.surface.get_current_texture() {
237 wgpu::CurrentSurfaceTexture::Success(texture) => texture,
238 wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
239 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Outdated => {
240 return Err(AcquireError::Transient);
241 }
242 other => {
243 return Err(AcquireError::Fatal(format!(
244 "unexpected surface state: {other:?}"
245 )));
246 }
247 };
248
249 let view = surface_texture
250 .texture
251 .create_view(&wgpu::TextureViewDescriptor::default());
252 let encoder = self
253 .device
254 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
255
256 Ok(WGPUFrame {
257 encoder,
258 view,
259 surface_texture,
260 })
261 }
262
263 fn present(&mut self, frame: Self::Frame) {
264 self.queue.submit(std::iter::once(frame.encoder.finish()));
265 frame.surface_texture.present();
266 }
267}
268
269pub struct WGPUPlugin {
270 config: WindowConfig,
271}
272
273impl WGPUPlugin {
274 pub fn new(config: WindowConfig) -> Self {
275 Self { config }
276 }
277}
278
279impl Plugin for WGPUPlugin {
280 fn build(&self, app: &mut App) {
281 app.add_plugin(crate::prelude::WindowPlugin::<WinitWindow>::new(
282 WindowConfig {
283 title: self.config.title.clone(),
284 width: self.config.width,
285 height: self.config.height,
286 },
287 ))
288 .add_plugin(crate::prelude::GraphicsPlugin::<WGPUBackend, WinitWindow>::new())
289 .add_plugin(crate::prelude::RenderPlugin::<WGPUBackend>::new())
290 .add_plugin(crate::wgpu::textures::TexturePlugin)
291 .add_plugin(crate::wgpu::texture_array::TextureArrayPlugin)
292 .add_plugin(crate::wgpu::cubemap::CubemapPlugin)
293 .add_plugin(crate::wgpu::mesh::MeshPlugin::new())
294 .add_plugin(crate::wgpu::material::MaterialPlugin::new())
295 .add_plugin(crate::wgpu::instance::MaterialInstancePlugin::new())
296 .add_plugin(crate::wgpu::compute::ComputePlugin::new())
297 .add_plugin(crate::wgpu::instance::ComputeInstancePlugin::new())
298 .add_plugin(crate::prelude::LazyResourcePlugin::<
299 WGPUBackend,
300 crate::wgpu::samplers::GlobalSamplers,
301 >::new());
302 }
303}