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_format::TextureFormat,
14 texture_view::TextureView,
15 window::WinitWindow,
16 },
17};
18
19pub struct WGPUBackend {
32 pub(crate) device: wgpu::Device,
33 pub(crate) queue: wgpu::Queue,
34 pub(crate) surface: wgpu::Surface<'static>,
35 pub(crate) config: wgpu::SurfaceConfiguration,
36 msaa_sample_count: u32,
37 msaa_color: Option<wgpu::TextureView>,
38}
39
40impl WGPUBackend {
41 pub fn surface_width(&self) -> u32 {
43 self.config.width
44 }
45
46 pub fn surface_height(&self) -> u32 {
48 self.config.height
49 }
50
51 pub fn surface_format(&self) -> TextureFormat {
54 self.config.format.into()
55 }
56
57 pub fn sample_count(&self) -> u32 {
63 self.msaa_sample_count
64 }
65
66 pub fn set_msaa(&mut self, sample_count: u32) {
78 self.msaa_sample_count = sample_count;
79 self.rebuild_msaa_color();
80 }
81
82 fn rebuild_msaa_color(&mut self) {
83 if self.msaa_sample_count <= 1 {
84 self.msaa_color = None;
85 return;
86 }
87 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
88 label: Some("pebble-msaa-color"),
89 size: wgpu::Extent3d { width: self.config.width, height: self.config.height, depth_or_array_layers: 1 },
90 mip_level_count: 1,
91 sample_count: self.msaa_sample_count,
92 dimension: wgpu::TextureDimension::D2,
93 format: self.config.format,
94 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
95 view_formats: &[],
96 });
97 self.msaa_color = Some(texture.create_view(&wgpu::TextureViewDescriptor::default()));
98 }
99}
100
101impl WGPUBackend {
102 async fn init_async(
103 handle: impl GPUSurfaceHandle,
104 width: u32,
105 height: u32,
106 sender: InitSender<Self>,
107 ) {
108 let backends = if cfg!(target_arch = "wasm32") {
109 wgpu::Backends::BROWSER_WEBGPU
110 } else {
111 wgpu::Backends::PRIMARY
112 };
113
114 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
115 display: None,
116 backends,
117 flags: wgpu::InstanceFlags::default(),
118 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
119 backend_options: wgpu::BackendOptions::default(),
120 });
121
122 let surface = instance.create_surface(handle).unwrap();
123
124 let adapter = instance
125 .request_adapter(&wgpu::RequestAdapterOptions {
126 power_preference: wgpu::PowerPreference::HighPerformance,
127 force_fallback_adapter: false,
128 compatible_surface: Some(&surface),
129 })
130 .await
131 .unwrap();
132
133 let (required_features, required_limits) = if cfg!(target_arch = "wasm32") {
134 (wgpu::Features::empty(), wgpu::Limits::defaults())
135 } else {
136 (
137 wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
138 wgpu::Limits::default(),
139 )
140 };
141
142 let (device, queue) = adapter
143 .request_device(&wgpu::DeviceDescriptor {
144 label: None,
145 required_features,
146 required_limits,
147 ..Default::default()
148 })
149 .await
150 .unwrap();
151
152 let caps = surface.get_capabilities(&adapter);
153 let format = caps
154 .formats
155 .iter()
156 .copied()
157 .find(|f| f.is_srgb())
158 .unwrap_or(caps.formats[0]);
159
160 let present_mode = caps
164 .present_modes
165 .iter()
166 .copied()
167 .find(|m| *m == wgpu::PresentMode::Fifo)
168 .unwrap_or(caps.present_modes[0]);
169
170 let config = wgpu::SurfaceConfiguration {
171 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
172 format,
173 present_mode,
174 alpha_mode: caps.alpha_modes[0],
175 width,
176 height,
177 desired_maximum_frame_latency: 2,
178 view_formats: vec![],
179 };
180 surface.configure(&device, &config);
181
182 sender.send(WGPUBackend {
183 device,
184 queue,
185 surface,
186 config,
187 msaa_sample_count: 1,
188 msaa_color: None,
189 });
190 }
191}
192
193pub struct WGPUFrame {
194 encoder: wgpu::CommandEncoder,
195 view: wgpu::TextureView,
196 surface_texture: wgpu::SurfaceTexture,
197 msaa_view: Option<wgpu::TextureView>,
202}
203
204impl FrameOperations for WGPUFrame {
205 type Context<'a> = RenderPass<'a>;
206 type Attachment = TextureView;
207 type DepthAttachment = TextureView;
208
209 fn begin(&mut self, pass: Pass<'_, Self>) -> Self::Context<'_> {
210 let color_attachments: Vec<_> = pass
211 .colors
212 .iter()
213 .map(|target| {
214 let (view, resolve_target, clear) = match target {
215 ColorTarget::Default { clear } => match &self.msaa_view {
216 Some(msaa) => (msaa, Some(&self.view), clear),
217 None => (&self.view, None, clear),
218 },
219 ColorTarget::Custom { attachment, clear } => (attachment.raw(), None, clear),
220 };
221 Some(wgpu::RenderPassColorAttachment {
222 view,
223 depth_slice: None,
224 resolve_target,
225 ops: wgpu::Operations {
226 load: clear
227 .map(|[r, g, b, a]| {
228 wgpu::LoadOp::Clear(wgpu::Color {
229 r: r as f64,
230 g: g as f64,
231 b: b as f64,
232 a: a as f64,
233 })
234 })
235 .unwrap_or(wgpu::LoadOp::Load),
236 store: wgpu::StoreOp::Store,
237 },
238 })
239 })
240 .collect();
241
242 let depth_stencil_attachment =
243 pass.depth
244 .as_ref()
245 .map(|d| wgpu::RenderPassDepthStencilAttachment {
246 view: d.attachment.raw(),
247 depth_ops: Some(wgpu::Operations {
248 load: d
249 .clear
250 .map(wgpu::LoadOp::Clear)
251 .unwrap_or(wgpu::LoadOp::Load),
252 store: wgpu::StoreOp::Store,
253 }),
254 stencil_ops: None,
255 });
256
257 let raw = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
258 label: None,
259 color_attachments: &color_attachments,
260 depth_stencil_attachment,
261 timestamp_writes: None,
262 occlusion_query_set: None,
263 multiview_mask: None,
264 });
265 RenderPass::new(raw)
266 }
267}
268
269impl WGPUFrame {
270 pub fn compute_pass(&mut self, label: Option<&str>) -> ComputePass<'_> {
272 let raw = self.encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
273 label,
274 timestamp_writes: None,
275 });
276 ComputePass::new(raw)
277 }
278}
279
280impl WGPUBackend {
281 pub fn create_command_encoder(&self, label: Option<&str>) -> CommandEncoder {
289 CommandEncoder::new(self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }))
290 }
291
292 pub fn submit(&self, encoder: CommandEncoder) {
294 self.queue.submit(std::iter::once(encoder.into_raw().finish()));
295 }
296}
297
298impl Backend for WGPUBackend {
299 type Frame = WGPUFrame;
300
301 fn init(handle: impl GPUSurfaceHandle, width: u32, height: u32, sender: InitSender<Self>) {
312 #[cfg(not(target_arch = "wasm32"))]
313 {
314 pollster::block_on(Self::init_async(handle, width, height, sender));
315 }
316
317 #[cfg(target_arch = "wasm32")]
318 {
319 wasm_bindgen_futures::spawn_local(Self::init_async(handle, width, height, sender));
320 }
321 }
322
323 fn resize(&mut self, width: u32, height: u32) {
324 if width == 0 || height == 0 {
325 return; }
327 self.config.width = width;
328 self.config.height = height;
329 self.surface.configure(&self.device, &self.config);
330 self.rebuild_msaa_color();
331 }
332
333 fn acquire(&mut self) -> Result<Self::Frame, AcquireError> {
334 let surface_texture = match self.surface.get_current_texture() {
335 wgpu::CurrentSurfaceTexture::Success(texture) => texture,
336 wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
337 wgpu::CurrentSurfaceTexture::Timeout
338 | wgpu::CurrentSurfaceTexture::Outdated
339 | wgpu::CurrentSurfaceTexture::Occluded => {
340 return Err(AcquireError::Transient);
341 }
342 other => {
343 return Err(AcquireError::Fatal(format!(
344 "unexpected surface state: {other:?}"
345 )));
346 }
347 };
348
349 let view = surface_texture
350 .texture
351 .create_view(&wgpu::TextureViewDescriptor::default());
352 let encoder = self
353 .device
354 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
355
356 Ok(WGPUFrame {
357 encoder,
358 view,
359 surface_texture,
360 msaa_view: self.msaa_color.clone(),
361 })
362 }
363
364 fn present(&mut self, frame: Self::Frame) {
365 self.queue.submit(std::iter::once(frame.encoder.finish()));
366 frame.surface_texture.present();
367 }
368}
369
370pub struct WGPUPlugin {
371 config: WindowConfig,
372}
373
374impl WGPUPlugin {
375 pub fn new(config: WindowConfig) -> Self {
376 Self { config }
377 }
378}
379
380impl Plugin for WGPUPlugin {
381 fn build(&self, app: &mut App) {
382 app.add_plugin(crate::prelude::WindowPlugin::<WinitWindow>::new(
383 WindowConfig {
384 title: self.config.title.clone(),
385 width: self.config.width,
386 height: self.config.height,
387 },
388 ))
389 .add_plugin(crate::prelude::GraphicsPlugin::<WGPUBackend, WinitWindow>::new())
390 .add_plugin(crate::prelude::RenderPlugin::<WGPUBackend>::new())
391 .add_plugin(crate::wgpu::textures::TexturePlugin)
392 .add_plugin(crate::wgpu::texture_array::TextureArrayPlugin)
393 .add_plugin(crate::wgpu::cubemap::CubemapPlugin)
394 .add_plugin(crate::wgpu::mesh::MeshPlugin::new())
395 .add_plugin(crate::wgpu::material::MaterialPlugin::new())
396 .add_plugin(crate::wgpu::instance::MaterialInstancePlugin::new())
397 .add_plugin(crate::wgpu::compute::ComputePlugin::new())
398 .add_plugin(crate::wgpu::instance::ComputeInstancePlugin::new())
399 .add_plugin(crate::prelude::LazyResourcePlugin::<
400 WGPUBackend,
401 crate::wgpu::samplers::GlobalSamplers,
402 >::new());
403 }
404}