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