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