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