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